package listing

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var (
	ErrDependencyUnavailable = errors.New("dependency unavailable")
	ErrInvalidInput          = errors.New("invalid listing input")
	ErrListingLocked         = errors.New("listing locked")
	ErrMissingTitle          = errors.New("missing listing title")
	ErrMissingServerRegion   = errors.New("missing server region")
	ErrInvalidPrice          = errors.New("invalid listing price")
	ErrInvalidDeposit        = errors.New("invalid listing deposit")
	ErrDepositTooLow         = errors.New("listing deposit too low")
	ErrInvalidHafCoin        = errors.New("invalid haf coin amount")
	ErrMissingScreenshot     = errors.New("missing screenshot")
	ErrMissingUploaderName   = errors.New("missing uploader name")
	ErrMissingUploadData     = errors.New("missing upload data")
	ErrUploaderNotFound      = errors.New("uploader not found")
	ErrUploaderAmbiguous     = errors.New("uploader ambiguous")
	ErrTooManyUploadItems    = errors.New("too many upload items")
)

type Service struct {
	repo   *Repository
	config ConfigReader
}

type ConfigReader interface {
	FindValue(key string) (string, error)
}

const (
	reviewRequiredConfigKey = "listing.review_required"
	publishOptionsConfigKey = "listing.publish_options"
	defaultFireLevelMin     = 38
	maxExternalUploadItems  = 10
	defaultUploadScreenshot = "/api/listings/default-upload-screenshot"
)

var priceNumberPattern = regexp.MustCompile(`(\d+(?:\.\d+)?)`)

type FireLevelTooLowError struct {
	Min int
}

func (e FireLevelTooLowError) Error() string {
	return "fire level too low"
}

type UploadValidationError struct {
	Missing []string
	Invalid []string
}

func (e UploadValidationError) Error() string {
	parts := make([]string, 0, 2)
	if len(e.Missing) > 0 {
		parts = append(parts, "缺少必填字段："+strings.Join(e.Missing, "、"))
	}
	if len(e.Invalid) > 0 {
		parts = append(parts, "字段格式不正确："+strings.Join(e.Invalid, "、"))
	}
	return strings.Join(parts, "；")
}

func NewService(repo *Repository, config ConfigReader) *Service {
	return &Service{repo: repo, config: config}
}

func (s *Service) Create(ownerID uint64, req CreateRequest) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	rules, err := s.publishRules()
	if err != nil {
		return nil, err
	}
	if err := validateRequest(req, rules); err != nil {
		return nil, err
	}
	reviewRequired, err := s.reviewRequired()
	if err != nil {
		return nil, err
	}
	return s.repo.Create(ownerID, req, reviewRequired)
}

func (s *Service) ImportExternalUpload(req ExternalUploadRequest, meta ExternalUploadMeta) (*ExternalUploadResponse, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	uploaderName := req.normalizedUploaderName()
	if uploaderName == "" {
		return nil, ErrMissingUploaderName
	}
	items, err := parseExternalUploadItems(req.Data)
	if err != nil {
		return nil, err
	}
	if len(items) > maxExternalUploadItems {
		return nil, ErrTooManyUploadItems
	}
	rules, err := s.publishRules()
	if err != nil {
		return nil, err
	}
	clientUploadTime := parseClientUploadTime(req.UploadTime)
	results := make([]ExternalUploadResult, 0, len(items))
	resp := &ExternalUploadResponse{Total: len(items)}
	for index, item := range items {
		createReq := externalAccountToCreateRequest(uploaderName, req.UploadTime, item)
		if err := validateRequest(createReq, rules); err != nil {
			if len(items) == 1 {
				return nil, err
			}
			resp.Failed++
			results = append(results, ExternalUploadResult{Index: index, Error: err.Error()})
			continue
		}
		parsedPayload, _ := json.Marshal(item)
		dto, err := s.repo.CreateFromExternalUpload(externalUploadCreate{
			UploaderName:     uploaderName,
			ClientUploadTime: clientUploadTime,
			ClientIP:         meta.IP,
			RawPayload:       meta.RawPayload,
			ParsedPayload:    parsedPayload,
		}, createReq)
		if err != nil {
			if len(items) == 1 {
				return nil, err
			}
			resp.Failed++
			results = append(results, ExternalUploadResult{Index: index, Error: err.Error()})
			continue
		}
		resp.Success++
		result := ExternalUploadResult{
			Index:        index,
			ListingID:    dto.ID,
			AccountID:    dto.AccountID,
			Status:       dto.Status,
			ReviewStatus: dto.ReviewStatus,
		}
		results = append(results, result)
		if len(items) == 1 {
			resp.ListingID = dto.ID
			resp.AccountID = dto.AccountID
			resp.Status = dto.Status
			resp.ReviewStatus = dto.ReviewStatus
		}
	}
	resp.Items = results
	if resp.Success == 0 && resp.Failed > 0 {
		return resp, ErrInvalidInput
	}
	return resp, nil
}

func (s *Service) Update(ownerID uint64, id uint64, req UpdateRequest) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	rules, err := s.publishRules()
	if err != nil {
		return nil, err
	}
	if err := validateRequest(req, rules); err != nil {
		return nil, err
	}
	reviewRequired, err := s.reviewRequired()
	if err != nil {
		return nil, err
	}
	return s.repo.Update(ownerID, id, req, reviewRequired)
}

func (s *Service) SubmitReview(ownerID uint64, id uint64) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	reviewRequired, err := s.reviewRequired()
	if err != nil {
		return nil, err
	}
	return s.repo.SubmitReview(ownerID, id, reviewRequired)
}

func (s *Service) ListPendingReview() ([]ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	return s.repo.ListPendingReview()
}

func (s *Service) ListAdmin(query AdminListQuery) (*AdminListResult, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	return s.repo.ListAdmin(query)
}

func (s *Service) FindAdmin(id uint64) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	return s.repo.FindAdmin(id)
}

func (s *Service) AdminOffline(adminID uint64, id uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	if req.Reason == "" {
		return nil, ErrInvalidInput
	}
	return s.repo.AdminOffline(adminID, id, req, meta)
}

func (s *Service) AdminMarkAbnormal(adminID uint64, id uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	if req.Reason == "" {
		return nil, ErrInvalidInput
	}
	return s.repo.AdminMarkAbnormal(adminID, id, req, meta)
}

func (s *Service) Approve(id uint64) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	return s.repo.Approve(id)
}

func (s *Service) AdjustReviewPrice(adminID uint64, id uint64, req AdminPriceAdjustRequest, meta AuditMeta) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	if req.BuyerRatio <= 0 && req.BuyerTotalPrice <= 0 {
		return nil, ErrInvalidPrice
	}
	return s.repo.AdjustReviewPrice(adminID, id, req, meta)
}

func (s *Service) Reject(id uint64, req ReviewRequest) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	if req.Reason == "" {
		return nil, ErrInvalidInput
	}
	return s.repo.Reject(id, req)
}

func (s *Service) Offline(ownerID uint64, id uint64) error {
	if s.repo == nil {
		return ErrDependencyUnavailable
	}
	return s.repo.Offline(ownerID, id)
}

func (s *Service) ListPublic(query PublicListQuery) (*PublicListResult, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	return s.repo.ListPublic(query)
}

func (s *Service) ListMine(ownerID uint64) ([]ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	return s.repo.ListMine(ownerID)
}

func (s *Service) FindPublic(id uint64) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	return s.repo.FindPublic(id)
}

func (s *Service) FindPublicCoverKey(id uint64) (string, error) {
	if s.repo == nil {
		return "", ErrDependencyUnavailable
	}
	return s.repo.FindPublicCoverKey(id)
}

func (s *Service) FindPublicScreenshotKey(id uint64, index int) (string, error) {
	if s.repo == nil {
		return "", ErrDependencyUnavailable
	}
	return s.repo.FindPublicScreenshotKey(id, index)
}

func (s *Service) FindMine(ownerID uint64, id uint64) (*ListingDTO, error) {
	if s.repo == nil {
		return nil, ErrDependencyUnavailable
	}
	return s.repo.FindMine(ownerID, id)
}

type publishRules struct {
	FireLevelMin int
}

func validateRequest(req CreateRequest, rules publishRules) error {
	if strings.TrimSpace(req.Title) == "" {
		return ErrMissingTitle
	}
	if strings.TrimSpace(req.ServerRegion) == "" {
		return ErrMissingServerRegion
	}
	if normalizedListingPrice(req) <= 0 {
		return ErrInvalidPrice
	}
	if req.DepositAmount < 0 {
		return ErrInvalidDeposit
	}
	if consumables := consumableValue(req.AssetSummary); consumables > 0 && req.DepositAmount <= consumables {
		return ErrDepositTooLow
	}
	if req.HafCoinAmount < 0 {
		return ErrInvalidHafCoin
	}
	if !hasScreenshotURL(req.ScreenshotURLS) {
		return ErrMissingScreenshot
	}
	if fireLevel, ok := readFireLevel(req.AssetSummary); ok && fireLevel < rules.FireLevelMin {
		return FireLevelTooLowError{Min: rules.FireLevelMin}
	}
	return nil
}

func parseExternalUploadItems(raw json.RawMessage) ([]ExternalAccountData, error) {
	if len(raw) == 0 || strings.TrimSpace(string(raw)) == "" || string(raw) == "null" {
		return nil, ErrMissingUploadData
	}
	var singleRaw map[string]json.RawMessage
	if err := json.Unmarshal(raw, &singleRaw); err == nil {
		if err := validateExternalUploadRaw(singleRaw, "data"); err != nil {
			return nil, err
		}
		var single ExternalAccountData
		if err := json.Unmarshal(raw, &single); err != nil {
			return nil, ErrMissingUploadData
		}
		return []ExternalAccountData{single}, nil
	}
	var rawItems []json.RawMessage
	if err := json.Unmarshal(raw, &rawItems); err != nil {
		return nil, ErrMissingUploadData
	}
	if len(rawItems) == 0 {
		return nil, ErrMissingUploadData
	}
	items := make([]ExternalAccountData, 0, len(rawItems))
	for index, rawItem := range rawItems {
		var rawMap map[string]json.RawMessage
		if err := json.Unmarshal(rawItem, &rawMap); err != nil {
			return nil, UploadValidationError{Invalid: []string{fmt.Sprintf("data[%d]", index)}}
		}
		prefix := fmt.Sprintf("data[%d]", index)
		if err := validateExternalUploadRaw(rawMap, prefix); err != nil {
			return nil, err
		}
		var item ExternalAccountData
		if err := json.Unmarshal(rawItem, &item); err != nil {
			return nil, UploadValidationError{Invalid: []string{prefix}}
		}
		items = append(items, item)
	}
	return items, nil
}

func validateExternalUploadRaw(raw map[string]json.RawMessage, prefix string) error {
	var validation UploadValidationError
	requireStringField(raw, prefix, "loginMethod", &validation)
	requireStringField(raw, prefix, "rank", &validation)
	requireNumberField(raw, prefix, "level", &validation)
	requireNumberField(raw, prefix, "safeSlots", &validation)
	requireNumberField(raw, prefix, "secretKD", &validation)
	requireNumberField(raw, prefix, "deposit", &validation)
	requireNumberField(raw, prefix, "dailyLossM", &validation)

	currency := requireObjectField(raw, prefix, "currency", &validation)
	requireNumberField(currency, prefix+".currency", "hafuCoin", &validation)
	requireNumberField(currency, prefix+".currency", "recycleRatio", &validation)
	requireNumberField(currency, prefix+".currency", "recycleRent", &validation)

	dailyConsumption := requireObjectField(raw, prefix, "dailyConsumption", &validation)
	requireNumberField(dailyConsumption, prefix+".dailyConsumption", "stamina", &validation)
	requireNumberField(dailyConsumption, prefix+".dailyConsumption", "weight", &validation)

	if len(validation.Missing) > 0 || len(validation.Invalid) > 0 {
		return validation
	}
	return nil
}

func requireObjectField(raw map[string]json.RawMessage, prefix string, field string, validation *UploadValidationError) map[string]json.RawMessage {
	if raw == nil {
		validation.Missing = append(validation.Missing, prefix+"."+field)
		return nil
	}
	value, ok := raw[field]
	if !ok || isJSONNull(value) {
		validation.Missing = append(validation.Missing, prefix+"."+field)
		return nil
	}
	var object map[string]json.RawMessage
	if err := json.Unmarshal(value, &object); err != nil {
		validation.Invalid = append(validation.Invalid, prefix+"."+field)
		return nil
	}
	return object
}

func requireStringField(raw map[string]json.RawMessage, prefix string, field string, validation *UploadValidationError) {
	value, ok := raw[field]
	if !ok || isJSONNull(value) {
		validation.Missing = append(validation.Missing, prefix+"."+field)
		return
	}
	var text string
	if err := json.Unmarshal(value, &text); err != nil {
		validation.Invalid = append(validation.Invalid, prefix+"."+field)
		return
	}
	if strings.TrimSpace(text) == "" {
		validation.Missing = append(validation.Missing, prefix+"."+field)
	}
}

func requireNumberField(raw map[string]json.RawMessage, prefix string, field string, validation *UploadValidationError) {
	if raw == nil {
		validation.Missing = append(validation.Missing, prefix+"."+field)
		return
	}
	value, ok := raw[field]
	if !ok || isJSONNull(value) {
		validation.Missing = append(validation.Missing, prefix+"."+field)
		return
	}
	var number json.Number
	decoder := json.NewDecoder(strings.NewReader(string(value)))
	decoder.UseNumber()
	if err := decoder.Decode(&number); err != nil {
		validation.Invalid = append(validation.Invalid, prefix+"."+field)
	}
}

func isJSONNull(value json.RawMessage) bool {
	return strings.TrimSpace(string(value)) == "null"
}

func parseClientUploadTime(value int64) *time.Time {
	if value <= 0 {
		return nil
	}
	if value > 1_000_000_000_000 {
		parsed := time.UnixMilli(value)
		return &parsed
	}
	parsed := time.Unix(value, 0)
	return &parsed
}

func externalAccountToCreateRequest(uploaderName string, uploadTime int64, item ExternalAccountData) CreateRequest {
	hafCoinM := item.Currency.HafuCoin
	price := item.Currency.RecycleRent
	ratio := item.Currency.RecycleRatio
	insurance := insuranceFromSafeSlots(item.SafeSlots)
	staminaLevel := levelText(item.DailyConsumption.Stamina)
	loadLevel := levelText(item.DailyConsumption.Weight)
	skins := cleanStrings(item.Inventory.Skins)
	assetSummary := map[string]any{
		"face_owner":       "",
		"secret_kd":        item.SecretKD,
		"fire_level":       item.Level,
		"daily_loss_m":     item.DailyLossM,
		"publish_ratio":    ratio,
		"season_insurance": insurance,
		"stamina_level":    staminaLevel,
		"load_level":       loadLevel,
		"resources":        externalResources(item.Inventory),
		"skin_groups":      externalSkinGroups(skins),
		"online_time_text": strings.TrimSpace(item.OwnerOnlineTime),
		"ban_record":       normalizeBanRecord(item.BanRecord),
		"common_regions":   commonRegions(item.CommonRegion),
		"remark":           "开放接口自动上传，等待后台审核。",
		"import_meta": map[string]any{
			"uploader_name":      uploaderName,
			"client_upload_time": uploadTime,
			"contact_phone":      strings.TrimSpace(item.ContactPhone),
		},
		"price_breakdown": map[string]any{
			"seller_reference_ratio": ratio,
			"seller_ratio":           ratio,
			"seller_coin_base_price": price,
			"seller_total_price":     price,
			"consumable_price":       consumableValue(map[string]any{"resources": externalResources(item.Inventory)}),
			"buyer_coin_base_price":  price,
			"buyer_total_price":      price,
			"buyer_ratio":            ratio,
			"platform_markup_amount": 0,
			"platform_rule_type":     "external_upload",
		},
	}
	return CreateRequest{
		Title:          externalUploadTitle(item, insurance, hafCoinM),
		Description:    "开放接口自动上传，等待后台审核。",
		ServerRegion:   serverRegionFromLoginMethod(item.LoginMethod),
		LoginPlatform:  strings.TrimSpace(item.LoginMethod),
		RankLevel:      strings.TrimSpace(item.Rank),
		HafCoinAmount:  int64(math.Round(hafCoinM * 1000000)),
		AssetSummary:   assetSummary,
		ScreenshotURLS: []string{defaultUploadScreenshot},
		Price:          price,
		DepositAmount:  item.Deposit,
	}
}

func externalUploadTitle(item ExternalAccountData, insurance string, hafCoinM float64) string {
	parts := []string{
		strings.TrimSpace(item.Rank),
		insurance,
		fmt.Sprintf("%.1fM", hafCoinM),
		strings.TrimSpace(item.LoginMethod),
	}
	title := strings.TrimSpace(strings.Join(cleanStrings(parts), " "))
	if title == "" {
		return "开放接口上传账号"
	}
	if len([]rune(title)) > 128 {
		return string([]rune(title)[:128])
	}
	return title
}

func serverRegionFromLoginMethod(value string) string {
	value = strings.TrimSpace(value)
	switch {
	case strings.Contains(value, "微信"):
		return "微信"
	case strings.Contains(strings.ToLower(value), "steam"):
		return "Steam"
	default:
		return "QQ"
	}
}

func insuranceFromSafeSlots(value int) string {
	switch value {
	case 9:
		return "3*3"
	case 6:
		return "2*3"
	case 4:
		return "2*2"
	case 2:
		return "2*1"
	default:
		if value > 0 {
			return strconv.Itoa(value) + "格"
		}
		return ""
	}
}

func levelText(value int) string {
	if value <= 0 {
		return ""
	}
	return strconv.Itoa(value) + "级"
}

func externalResources(inventory ExternalUploadInventory) []any {
	resources := []any{
		map[string]any{"key": "awmAmmo", "label": "AWM子弹", "price": "0.6元/发", "quantity": inventory.AWMBullets, "mode": "收费"},
		map[string]any{"key": "helmet6", "label": "6头", "price": "1.5元/个", "quantity": inventory.Level6Helmets, "mode": "收费"},
		map[string]any{"key": "armor6", "label": "6甲", "price": "2.5元/个", "quantity": inventory.Level6Armor, "mode": "收费"},
	}
	return resources
}

func externalSkinGroups(skins []string) map[string][]string {
	groups := map[string][]string{
		"melee":    {},
		"imported": {},
	}
	for _, skin := range skins {
		switch skin {
		case "坠星者", "暗星", "龙牙", "信条", "怜悯", "赤枭", "影锋", "黑海", "北极星", "电锯惊魂", "处刑者":
			groups["melee"] = append(groups["melee"], skin)
		default:
			groups["imported"] = append(groups["imported"], skin)
		}
	}
	return groups
}

func normalizeBanRecord(value string) string {
	value = strings.TrimSpace(value)
	switch value {
	case "", "无", "无封禁", "无封禁记录":
		return "无封禁记录"
	default:
		return value
	}
}

func commonRegions(value string) []string {
	return cleanStrings(strings.Split(value, ","))
}

func cleanStrings(values []string) []string {
	result := make([]string, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		result = append(result, value)
	}
	return result
}

func consumableValue(summary map[string]any) float64 {
	if summary == nil {
		return 0
	}
	rawResources, ok := summary["resources"]
	if !ok {
		return 0
	}
	resources, ok := rawResources.([]any)
	if !ok {
		return 0
	}
	total := 0.0
	for _, raw := range resources {
		resource, ok := raw.(map[string]any)
		if !ok {
			continue
		}
		mode, _ := resource["mode"].(string)
		if strings.TrimSpace(mode) != "收费" {
			continue
		}
		quantity := readSummaryFloat(resource["quantity"])
		if quantity <= 0 {
			continue
		}
		priceText, _ := resource["price"].(string)
		total += quantity * readUnitPrice(priceText)
	}
	return roundMoney(total)
}

func readSummaryFloat(value any) float64 {
	switch current := value.(type) {
	case float64:
		return current
	case int:
		return float64(current)
	case int64:
		return float64(current)
	case json.Number:
		parsed, err := current.Float64()
		if err != nil {
			return 0
		}
		return parsed
	case string:
		parsed, err := strconv.ParseFloat(strings.TrimSpace(current), 64)
		if err != nil {
			return 0
		}
		return parsed
	default:
		return 0
	}
}

func readUnitPrice(priceText string) float64 {
	numbers := priceNumberPattern.FindAllString(priceText, -1)
	if len(numbers) == 0 {
		return 0
	}
	amount, err := strconv.ParseFloat(numbers[0], 64)
	if err != nil {
		return 0
	}
	if len(numbers) >= 2 {
		count, err := strconv.ParseFloat(numbers[1], 64)
		if err == nil && count > 0 {
			return amount / count
		}
	}
	return amount
}

func readFireLevel(summary map[string]any) (int, bool) {
	if summary == nil {
		return 0, false
	}
	value, ok := summary["fire_level"]
	if !ok {
		return 0, false
	}
	switch current := value.(type) {
	case float64:
		return int(current), true
	case int:
		return current, true
	case string:
		parsed, err := strconv.Atoi(strings.TrimSpace(current))
		return parsed, err == nil
	default:
		return 0, false
	}
}

func hasScreenshotURL(urls []string) bool {
	for _, url := range urls {
		if strings.TrimSpace(url) != "" {
			return true
		}
	}
	return false
}

func (s *Service) reviewRequired() (bool, error) {
	if s.config == nil {
		return false, nil
	}
	value, err := s.config.FindValue(reviewRequiredConfigKey)
	if err != nil {
		return false, err
	}
	required, err := strconv.ParseBool(strings.TrimSpace(value))
	if err != nil {
		return false, nil
	}
	return required, nil
}

func (s *Service) publishRules() (publishRules, error) {
	rules := publishRules{FireLevelMin: defaultFireLevelMin}
	if s.config == nil {
		return rules, nil
	}
	value, err := s.config.FindValue(publishOptionsConfigKey)
	if err != nil {
		return rules, err
	}
	var raw struct {
		FireLevelMin int `json:"fire_level_min"`
	}
	if err := json.Unmarshal([]byte(value), &raw); err != nil {
		return rules, nil
	}
	if raw.FireLevelMin > 0 {
		rules.FireLevelMin = raw.FireLevelMin
	}
	return rules, nil
}
