package middleware

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/hex"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/store/postgres"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	pkgmiddleware "github.com/break/junhong_cmp_fiber/pkg/middleware"
	"github.com/gofiber/fiber/v2"
	"github.com/redis/go-redis/v9"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

// AgentOpenAPIAuthConfig 代理开放接口认证中间件配置
type AgentOpenAPIAuthConfig struct {
	AccountStore *postgres.AccountStore
	ShopStore    *postgres.ShopStore
	Redis        *redis.Client
}

// NewAgentOpenAPIAuthMiddleware 创建代理开放接口认证中间件
func NewAgentOpenAPIAuthMiddleware(config AgentOpenAPIAuthConfig) fiber.Handler {
	return func(c *fiber.Ctx) error {
		if c.Method() == fiber.MethodOptions {
			return c.SendStatus(fiber.StatusNoContent)
		}

		credentials := extractAgentOpenAPICredentials(c)
		if err := credentials.validateRequired(); err != nil {
			return err
		}

		account, err := authenticateAgentAccountForOpenAPI(c.UserContext(), config, credentials)
		if err != nil {
			return err
		}

		if err := validateAgentOpenAPITimestamp(credentials.Timestamp, time.Now()); err != nil {
			return err
		}

		if err := reserveAgentOpenAPINonce(c.UserContext(), config.Redis, credentials.Account, credentials.Nonce); err != nil {
			return err
		}

		payload := buildAgentOpenAPISignPayload(c, credentials)
		if err := verifyAgentOpenAPISign(payload, credentials.Password, credentials.Sign); err != nil {
			return err
		}

		if err := injectAgentOpenAPIContext(c, config.ShopStore, account); err != nil {
			return err
		}

		return c.Next()
	}
}

type agentOpenAPICredentials struct {
	Account   string
	Password  string
	Timestamp string
	Nonce     string
	Sign      string
}

func extractAgentOpenAPICredentials(c *fiber.Ctx) agentOpenAPICredentials {
	return agentOpenAPICredentials{
		Account:   strings.TrimSpace(c.Get(constants.AgentOpenAPIHeaderAccount)),
		Password:  c.Get(constants.AgentOpenAPIHeaderPassword),
		Timestamp: strings.TrimSpace(c.Get(constants.AgentOpenAPIHeaderTimestamp)),
		Nonce:     strings.TrimSpace(c.Get(constants.AgentOpenAPIHeaderNonce)),
		Sign:      strings.TrimSpace(c.Get(constants.AgentOpenAPIHeaderSign)),
	}
}

func (c agentOpenAPICredentials) validateRequired() error {
	if c.Account == "" || c.Password == "" || c.Timestamp == "" || c.Nonce == "" || c.Sign == "" {
		return errors.New(errors.CodeOpenAPIAuthFailed)
	}
	return nil
}

func authenticateAgentAccountForOpenAPI(ctx context.Context, config AgentOpenAPIAuthConfig, credentials agentOpenAPICredentials) (*model.Account, error) {
	if config.AccountStore == nil {
		return nil, errors.New(errors.CodeInternalError, "开放接口认证依赖未配置")
	}

	account, err := config.AccountStore.GetByUsernameOrPhone(ctx, credentials.Account)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeOpenAPIAuthFailed)
		}
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询开放接口账号失败")
	}

	if account.UserType != constants.UserTypeAgent || account.ShopID == nil || *account.ShopID == 0 {
		return nil, errors.New(errors.CodeForbidden, "开放接口仅允许代理账号调用")
	}
	if account.Status != constants.StatusEnabled {
		return nil, errors.New(errors.CodeOpenAPIAuthFailed)
	}
	if config.ShopStore == nil {
		return nil, errors.New(errors.CodeInternalError, "开放接口店铺认证依赖未配置")
	}
	shop, err := config.ShopStore.GetByID(ctx, *account.ShopID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeOpenAPIAuthFailed)
		}
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询开放接口代理店铺失败")
	}
	if shop.Status != constants.StatusEnabled {
		return nil, errors.New(errors.CodeOpenAPIAuthFailed)
	}
	if err := bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(credentials.Password)); err != nil {
		return nil, errors.New(errors.CodeOpenAPIAuthFailed)
	}

	return account, nil
}

func validateAgentOpenAPITimestamp(raw string, now time.Time) error {
	requestTime, err := parseAgentOpenAPITimestamp(raw)
	if err != nil {
		return errors.New(errors.CodeOpenAPITimestampInvalid)
	}
	diff := now.Sub(requestTime)
	if diff < 0 {
		diff = -diff
	}
	if diff > constants.AgentOpenAPISignatureWindow {
		return errors.New(errors.CodeOpenAPITimestampInvalid)
	}
	return nil
}

func parseAgentOpenAPITimestamp(raw string) (time.Time, error) {
	if ts, err := strconv.ParseInt(raw, 10, 64); err == nil {
		if len(raw) == 13 {
			return time.UnixMilli(ts), nil
		}
		return time.Unix(ts, 0), nil
	}
	return time.Parse(time.RFC3339, raw)
}

func reserveAgentOpenAPINonce(ctx context.Context, redisClient *redis.Client, account, nonce string) error {
	if redisClient == nil {
		return errors.New(errors.CodeInternalError, "Redis 未配置")
	}

	key := constants.RedisAgentOpenAPINonceKey(account, nonce)
	ok, err := redisClient.SetNX(ctx, key, "1", constants.AgentOpenAPISignatureWindow).Result()
	if err != nil {
		return errors.Wrap(errors.CodeRedisError, err, "记录开放接口 nonce 失败")
	}
	if !ok {
		return errors.New(errors.CodeOpenAPINonceReplay)
	}
	return nil
}

func buildAgentOpenAPISignPayload(c *fiber.Ctx, credentials agentOpenAPICredentials) string {
	bodyHash := sha256.Sum256(c.Body())
	parts := []string{
		strings.ToUpper(c.Method()),
		c.Path(),
		canonicalAgentOpenAPIQuery(string(c.Request().URI().QueryString())),
		hex.EncodeToString(bodyHash[:]),
		credentials.Timestamp,
		credentials.Nonce,
		credentials.Account,
	}
	return strings.Join(parts, "\n")
}

func canonicalAgentOpenAPIQuery(rawQuery string) string {
	values, err := url.ParseQuery(rawQuery)
	if err != nil || len(values) == 0 {
		return ""
	}
	for key := range values {
		if strings.EqualFold(key, "sign") {
			delete(values, key)
		}
	}
	keys := make([]string, 0, len(values))
	for key := range values {
		keys = append(keys, key)
		sort.Strings(values[key])
	}
	sort.Strings(keys)

	pairs := make([]string, 0, len(keys))
	for _, key := range keys {
		for _, value := range values[key] {
			pairs = append(pairs, url.QueryEscape(key)+"="+url.QueryEscape(value))
		}
	}
	return strings.Join(pairs, "&")
}

func verifyAgentOpenAPISign(payload, password, providedSign string) error {
	mac := hmac.New(sha256.New, []byte(password))
	_, _ = mac.Write([]byte(payload))
	expectedSign := hex.EncodeToString(mac.Sum(nil))
	if subtle.ConstantTimeCompare([]byte(expectedSign), []byte(strings.ToLower(providedSign))) != 1 {
		return errors.New(errors.CodeOpenAPISignInvalid)
	}
	return nil
}

func injectAgentOpenAPIContext(c *fiber.Ctx, shopStore *postgres.ShopStore, account *model.Account) error {
	shopID := uint(0)
	if account.ShopID != nil {
		shopID = *account.ShopID
	}
	shopIDs := []uint{shopID}
	if shopStore != nil && shopID > 0 {
		var err error
		shopIDs, err = shopStore.GetSubordinateShopIDs(c.UserContext(), shopID)
		if err != nil || len(shopIDs) == 0 {
			shopIDs = []uint{shopID}
		}
	}

	info := &pkgmiddleware.UserContextInfo{
		UserID:             account.ID,
		UserType:           constants.UserTypeAgent,
		Username:           account.Username,
		ShopID:             shopID,
		SubordinateShopIDs: shopIDs,
	}
	pkgmiddleware.SetUserToFiberContext(c, info)
	return nil
}
