package domain

import (
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
)

func NowNano() int64 { return time.Now().UTC().UnixNano() }

func NewID() string { return uuid.NewString() }

const (
	MaxServiceItems = 20
	MaxServiceCases = 20
	MaxFaqItems     = 30
	MaxForbidden    = 50
	MaxServiceAreas = 22
	MaxTextLen      = 2000
	MaxShortTextLen = 200
)

/*
台灣縣市代碼白名單（ISO 3166-2:TW 的兩／三碼）。

判定時只做代碼相等比對，不做字串模糊比對，也不從貼文猜測縣市 —— 猜錯會把外地需求
判成在地需求，而使用者要到回覆送出後才發現。判不出來就是 unknown（OP-04），
unknown 不等於 mismatch。
*/
var serviceAreaCodes = map[string]string{
	"TPE": "臺北市",
	"NWT": "新北市",
	"TAO": "桃園市",
	"TXG": "臺中市",
	"TNN": "臺南市",
	"KHH": "高雄市",
	"KEE": "基隆市",
	"HSZ": "新竹市",
	"HSQ": "新竹縣",
	"MIA": "苗栗縣",
	"CHA": "彰化縣",
	"NAN": "南投縣",
	"YUN": "雲林縣",
	"CYI": "嘉義市",
	"CYQ": "嘉義縣",
	"PIF": "屏東縣",
	"ILA": "宜蘭縣",
	"HUA": "花蓮縣",
	"TTT": "臺東縣",
	"PEN": "澎湖縣",
	"KIN": "金門縣",
	"LIE": "連江縣",
}

func IsServiceAreaCode(code string) bool {
	_, ok := serviceAreaCodes[strings.ToUpper(strings.TrimSpace(code))]
	return ok
}

func ServiceAreaLabel(code string) string {
	return serviceAreaCodes[strings.ToUpper(strings.TrimSpace(code))]
}

type ServiceItem struct {
	Name     string  `bson:"name" json:"name"`
	PriceMin float64 `bson:"price_min,omitempty" json:"price_min,omitempty"`
	PriceMax float64 `bson:"price_max,omitempty" json:"price_max,omitempty"`
	Currency string  `bson:"currency,omitempty" json:"currency,omitempty"`
}

type ServiceCase struct {
	Title   string `bson:"title" json:"title"`
	Summary string `bson:"summary,omitempty" json:"summary,omitempty"`
	Link    string `bson:"link,omitempty" json:"link,omitempty"`
}

type FaqItem struct {
	Question string `bson:"question" json:"question"`
	Answer   string `bson:"answer" json:"answer"`
}

/*
ServiceProfile 每會員一份，_id 就是 owner_uid。

這份檔案是判定與回覆生成的加分輸入：沒有它仍可用關鍵字巡邏，只是比對與回覆較通用。
*/
type ServiceProfile struct {
	OwnerUID     int64         `bson:"_id" json:"owner_uid"`
	Services     []ServiceItem `bson:"services" json:"services"`
	Cases        []ServiceCase `bson:"cases" json:"cases"`
	Forbidden    []string      `bson:"forbidden" json:"forbidden"`
	Faq          []FaqItem     `bson:"faq" json:"faq"`
	ServiceAreas []string      `bson:"service_areas" json:"service_areas"`
	RemoteOk     bool          `bson:"remote_ok" json:"remote_ok"`
	Availability string        `bson:"availability,omitempty" json:"availability,omitempty"`
	ToneNote     string        `bson:"tone_note,omitempty" json:"tone_note,omitempty"`
	CreatedAt    int64         `bson:"created_at" json:"created_at"`
	UpdatedAt    int64         `bson:"updated_at" json:"updated_at"`
}

/*
Normalize 清掉空白與重複，並驗證。整份覆寫語意：呼叫端送什麼就是全貌。

錯誤一律包 ErrValidation 並指名欄位，因為使用者看到的是表單，訊息要能對到欄位。
*/
func (p *ServiceProfile) Normalize() error {
	if p.OwnerUID <= 0 {
		return fmt.Errorf("%w: owner_uid required", ErrValidation)
	}

	services := make([]ServiceItem, 0, len(p.Services))
	seenService := map[string]bool{}
	for i, s := range p.Services {
		s.Name = strings.TrimSpace(s.Name)
		if s.Name == "" {
			return fmt.Errorf("%w: services[%d].name required", ErrValidation, i)
		}
		if len(s.Name) > MaxShortTextLen {
			return fmt.Errorf("%w: services[%d].name too long", ErrValidation, i)
		}
		if s.PriceMin < 0 || s.PriceMax < 0 {
			return fmt.Errorf("%w: services[%d] price must not be negative", ErrValidation, i)
		}
		// 兩端都填才比大小；只填一端是「起價」或「上限」，都合法。
		if s.PriceMin > 0 && s.PriceMax > 0 && s.PriceMin > s.PriceMax {
			return fmt.Errorf("%w: services[%d] price_min must not exceed price_max", ErrValidation, i)
		}
		s.Currency = strings.ToUpper(strings.TrimSpace(s.Currency))
		if s.Currency == "" && (s.PriceMin > 0 || s.PriceMax > 0) {
			s.Currency = "TWD"
		}
		if s.Currency != "" && len(s.Currency) != 3 {
			return fmt.Errorf("%w: services[%d].currency must be a 3-letter code", ErrValidation, i)
		}
		key := strings.ToLower(s.Name)
		if seenService[key] {
			continue
		}
		seenService[key] = true
		services = append(services, s)
	}
	if len(services) == 0 {
		return fmt.Errorf("%w: services required", ErrValidation)
	}
	if len(services) > MaxServiceItems {
		return fmt.Errorf("%w: services exceeds %d items", ErrValidation, MaxServiceItems)
	}
	p.Services = services

	cases := make([]ServiceCase, 0, len(p.Cases))
	for i, c := range p.Cases {
		c.Title = strings.TrimSpace(c.Title)
		c.Summary = strings.TrimSpace(c.Summary)
		c.Link = strings.TrimSpace(c.Link)
		if c.Title == "" {
			return fmt.Errorf("%w: cases[%d].title required", ErrValidation, i)
		}
		if len(c.Summary) > MaxTextLen {
			return fmt.Errorf("%w: cases[%d].summary too long", ErrValidation, i)
		}
		if c.Link != "" && !strings.HasPrefix(c.Link, "http://") && !strings.HasPrefix(c.Link, "https://") {
			return fmt.Errorf("%w: cases[%d].link must be http(s)", ErrValidation, i)
		}
		cases = append(cases, c)
	}
	if len(cases) > MaxServiceCases {
		return fmt.Errorf("%w: cases exceeds %d items", ErrValidation, MaxServiceCases)
	}
	p.Cases = cases

	p.Forbidden = dedupeStrings(p.Forbidden)
	if len(p.Forbidden) > MaxForbidden {
		return fmt.Errorf("%w: forbidden exceeds %d items", ErrValidation, MaxForbidden)
	}
	for i, f := range p.Forbidden {
		if len(f) > MaxShortTextLen {
			return fmt.Errorf("%w: forbidden[%d] too long", ErrValidation, i)
		}
	}

	faq := make([]FaqItem, 0, len(p.Faq))
	for i, f := range p.Faq {
		f.Question = strings.TrimSpace(f.Question)
		f.Answer = strings.TrimSpace(f.Answer)
		if f.Question == "" || f.Answer == "" {
			return fmt.Errorf("%w: faq[%d] needs both question and answer", ErrValidation, i)
		}
		if len(f.Question) > MaxTextLen || len(f.Answer) > MaxTextLen {
			return fmt.Errorf("%w: faq[%d] too long", ErrValidation, i)
		}
		faq = append(faq, f)
	}
	if len(faq) > MaxFaqItems {
		return fmt.Errorf("%w: faq exceeds %d items", ErrValidation, MaxFaqItems)
	}
	p.Faq = faq

	areas := make([]string, 0, len(p.ServiceAreas))
	seenArea := map[string]bool{}
	for _, a := range p.ServiceAreas {
		code := strings.ToUpper(strings.TrimSpace(a))
		if code == "" {
			continue
		}
		if !IsServiceAreaCode(code) {
			return fmt.Errorf("%w: service_areas contains unknown code %q", ErrValidation, code)
		}
		if seenArea[code] {
			continue
		}
		seenArea[code] = true
		areas = append(areas, code)
	}
	if len(areas) > MaxServiceAreas {
		return fmt.Errorf("%w: service_areas exceeds %d items", ErrValidation, MaxServiceAreas)
	}
	// 既不接遠端也沒填地區，判定的地區這一問沒有任何依據可用。
	if len(areas) == 0 && !p.RemoteOk {
		return fmt.Errorf("%w: service_areas required unless remote_ok", ErrValidation)
	}
	p.ServiceAreas = areas

	p.Availability = strings.TrimSpace(p.Availability)
	p.ToneNote = strings.TrimSpace(p.ToneNote)
	if len(p.Availability) > MaxTextLen {
		return fmt.Errorf("%w: availability too long", ErrValidation)
	}
	if len(p.ToneNote) > MaxTextLen {
		return fmt.Errorf("%w: tone_note too long", ErrValidation)
	}
	return nil
}

// MatchesRegion 回報這份檔案是否服務某縣市代碼。空代碼代表判不出來，交給呼叫端當 unknown 處理。
func (p *ServiceProfile) MatchesRegion(code string) bool {
	if p.RemoteOk {
		return true
	}
	code = strings.ToUpper(strings.TrimSpace(code))
	if code == "" {
		return false
	}
	for _, a := range p.ServiceAreas {
		if a == code {
			return true
		}
	}
	return false
}

func dedupeStrings(in []string) []string {
	out := make([]string, 0, len(in))
	seen := map[string]bool{}
	for _, s := range in {
		s = strings.TrimSpace(s)
		if s == "" {
			continue
		}
		key := strings.ToLower(s)
		if seen[key] {
			continue
		}
		seen[key] = true
		out = append(out, s)
	}
	return out
}
