package commerce

import (
	"context"
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strings"
	"time"
)

type DownstreamHandler struct {
	service *Service
	logger  *slog.Logger
}

func NewDownstreamHandler(service *Service, logger *slog.Logger) *DownstreamHandler {
	return &DownstreamHandler{service: service, logger: logger}
}

func (h *DownstreamHandler) SyncHost(w http.ResponseWriter, r *http.Request) {
	payload, err := downstreamPayload(r)
	if err != nil {
		writeLegacySync(w, 400, "invalid payload")
		return
	}
	data, err := h.service.SyncDownstreamHost(r.Context(), payload)
	if err != nil {
		if h.logger != nil {
			h.logger.Warn("downstream host sync rejected", "error", err)
		}
		writeLegacySync(w, 400, err.Error())
		return
	}
	writeJSON(w, http.StatusOK, data)
}

func downstreamPayload(r *http.Request) (map[string]any, error) {
	contentType := strings.ToLower(r.Header.Get("Content-Type"))
	if strings.Contains(contentType, "application/json") {
		var out map[string]any
		if err := json.NewDecoder(r.Body).Decode(&out); err != nil {
			return nil, err
		}
		return out, nil
	}
	raw, err := io.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	if len(raw) == 0 {
		return map[string]any{}, nil
	}
	values, err := url.ParseQuery(string(raw))
	if err != nil {
		return nil, err
	}
	out := map[string]any{}
	for key, vals := range values {
		if len(vals) > 0 {
			out[key] = vals[0]
		}
	}
	return out, nil
}

func writeLegacySync(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, http.StatusOK, map[string]any{"status": status, "msg": msg})
}

func writeJSON(w http.ResponseWriter, status int, data any) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(data)
}

func (s *Service) SyncDownstreamHost(ctx context.Context, payload map[string]any) (map[string]any, error) {
	instanceID, ok := int64FromAny(payload["id"])
	if !ok || instanceID <= 0 {
		return nil, BadRequest("invalid downstream id")
	}
	var instance Instance
	if err := s.db.WithContext(ctx).Where("id = ?", instanceID).First(&instance).Error; err != nil {
		return nil, NotFound("instance not found")
	}
	token := strings.TrimSpace(valueOrDefault(instance.DownstreamToken, stableDownstreamToken(instance)))
	if !validDownstreamSignature(payload, token) {
		return nil, BadRequest("signature invalid")
	}
	updates := map[string]any{}
	if instance.DownstreamToken == nil || strings.TrimSpace(*instance.DownstreamToken) == "" {
		updates["downstream_token"] = token
	}
	if hostID, ok := int64FromAny(payload["host_id"]); ok && hostID > 0 {
		updates["upstream_id"] = hostID
		instance.UpstreamID = &hostID
	}
	if hostname := stringAny(payload["hostname"]); hostname != "" {
		updates["hostname"] = hostname
	}
	if mainIP := firstString(payload["mainip"], payload["main_ip"], payload["ip"]); strings.TrimSpace(mainIP) != "" {
		updates["main_ip"] = strings.TrimSpace(mainIP)
	}
	if port, ok := int64FromAny(firstNonNil(payload["port"], payload["ssh_port"])); ok && port > 0 {
		updates["ssh_port"] = int(port)
	}
	if status := downstreamStatus(payload["domainstatus"]); status != "" {
		updates["status"] = status
		if status == "active" && instance.UpstreamID != nil {
			updates["provision_log"] = fmt.Sprintf("zjmf downstream sync active host_id=%d", *instance.UpstreamID)
		} else {
			updates["provision_log"] = "zjmf downstream sync status=" + status
		}
	}
	stored := sanitizeDownstreamPayload(payload)
	stored["received_at"] = time.Now().UTC().Format(time.RFC3339)
	current := jsonMap(instance.UpstreamPayload)
	if current == nil {
		current = map[string]any{}
	}
	current["zjmf_downstream_host_sync"] = stored
	updates["upstream_payload"] = jsonRawFromAny(current)
	if err := s.db.WithContext(ctx).Model(&Instance{}).Where("id = ?", instance.ID).Updates(updates).Error; err != nil {
		return nil, err
	}
	return map[string]any{"status": 200, "msg": "sync success"}, nil
}

func validDownstreamSignature(payload map[string]any, token string) bool {
	signature := strings.ToUpper(strings.TrimSpace(stringAny(payload["signature"])))
	randStr := strings.TrimSpace(stringAny(payload["rand_str"]))
	if signature == "" || randStr == "" || payload["id"] == nil {
		return false
	}
	candidates := []any{stringAny(payload["id"])}
	if id, ok := int64FromAny(payload["id"]); ok {
		candidates = append(candidates, id)
	}
	for _, id := range candidates {
		if phpJSONMD5(map[string]any{"id": id, "rand_str": randStr, "token": token}) == signature {
			return true
		}
	}
	return false
}

func phpJSONMD5(data map[string]any) string {
	keys := make([]string, 0, len(data))
	for key := range data {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	parts := make([]string, 0, len(keys))
	for _, key := range keys {
		raw, _ := json.Marshal(data[key])
		parts = append(parts, fmt.Sprintf("%q:%s", key, raw))
	}
	sum := md5.Sum([]byte("{" + strings.Join(parts, ",") + "}"))
	return strings.ToUpper(hex.EncodeToString(sum[:]))
}

// stableDownstreamToken mirrors stableZJMFDownstreamToken (internal/jobs/provision.go):
// HMAC-SHA256 keyed with JWT_SECRET_KEY, not a bare hash of guessable integer IDs.
func stableDownstreamToken(instance Instance) string {
	secret := []byte(os.Getenv("JWT_SECRET_KEY"))
	mac := hmac.New(sha256.New, secret)
	_, _ = mac.Write([]byte(fmt.Sprintf("zjmf:%d:%d:%d", instance.ID, instance.OrderID, instance.ProductID)))
	return hex.EncodeToString(mac.Sum(nil))[:32]
}

func downstreamStatus(value any) string {
	switch strings.ToLower(strings.TrimSpace(stringAny(value))) {
	case "active":
		return "active"
	case "suspended":
		return "suspended"
	case "deleted", "cancelled":
		return "terminated"
	case "fraud", "failed":
		return "provision_failed"
	default:
		return ""
	}
}

func sanitizeDownstreamPayload(payload map[string]any) map[string]any {
	out := map[string]any{}
	for key, value := range payload {
		lower := strings.ToLower(key)
		if lower == "password" || lower == "rootpassword" || lower == "ippassword" {
			continue
		}
		out[key] = value
	}
	return out
}

func stringAny(value any) string {
	text := strings.TrimSpace(fmt.Sprint(value))
	if text == "<nil>" {
		return ""
	}
	return text
}
