package award

import (
	"encoding/json"
	"fmt"
	"testing"
	"time"

	"hamlog/internal/qso"
)

// A synthetic log with the shape a real one has: a spread of bands, modes,
// entities, and the free-text fields the harder awards actually scan.
func benchLog(n int) []qso.QSO {
	bands := []string{"160m", "80m", "40m", "30m", "20m", "17m", "15m", "12m", "10m", "6m"}
	modes := []string{"SSB", "CW", "FT8", "FT4", "RTTY"}
	entities := []int{291, 248, 227, 230, 339, 5, 108, 110, 6, 281}
	towns := []string{"SERIATE (BG)", "MILANO (MI)", "ROMA (RM)", "TORINO (TO)", "NAPOLI (NA)"}
	out := make([]qso.QSO, n)
	base := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)
	for i := range out {
		e := entities[i%len(entities)]
		out[i] = qso.QSO{
			ID:       int64(i + 1),
			Callsign: fmt.Sprintf("I%dABC", i%10),
			Band:     bands[i%len(bands)],
			Mode:     modes[i%len(modes)],
			DXCC:     &e,
			QSODate:  base.Add(time.Duration(i) * time.Hour),
			QTH:      towns[i%len(towns)],
			Address:  "Via Roma 12, 24068 " + towns[i%len(towns)] + " Italy",
			Grid:     "JN45AA",
			State:    "CA",
			LOTWRcvd: "Y",
		}
	}
	return out
}

// WAIP is the worst realistic shape: a predefined list, an exact-match primary
// that always produces a doomed candidate, then a fallback chain that tokenises a
// free-text field. If anything is slow, it is this.
func benchWAIP() (Def, []RefMeta) {
	d := Def{
		Code: "WAIP", Valid: true, Type: TypeReference,
		Field: "address", MatchBy: "code", ExactMatch: true,
		OrRules: []OrRule{
			{Field: "qth", MatchBy: "description"},
			{Field: "qth", MatchBy: "code"},
			{Field: "address", MatchBy: "code"},
		},
		DXCCFilter: []int{248},
		Confirm:    []string{"lotw", "qsl"},
	}
	codes := []string{"AG", "AL", "AN", "AO", "AP", "AQ", "AR", "AT", "AV", "BA", "BG", "BI", "BL", "BN", "BO", "BR", "BS", "BT", "BZ", "CA", "CB", "CE", "CH", "CL", "CN", "CO", "CR", "CS", "CT", "CZ", "EN", "FC", "FE", "FG", "FI", "FM", "FR", "GE", "GO", "GR", "IM", "IS", "KR", "LC", "LE", "LI", "LO", "LT", "LU", "MB", "MC", "ME", "MI", "MN", "MO", "MS", "MT", "NA", "NO", "NU", "OR", "PA", "PC", "PD", "PE", "PG", "PI", "PN", "PO", "PR", "PT", "PU", "PV", "PZ", "RA", "RC", "RE", "RG", "RI", "RM", "RN", "RO", "SA", "SI", "SO", "SP", "SR", "SS", "SU", "SV", "TA", "TE", "TN", "TO", "TP", "TR", "TS", "TV", "UD", "VA", "VB", "VC", "VE", "VI", "VR", "VT", "VV"}
	metas := make([]RefMeta, 0, len(codes))
	for _, c := range codes {
		metas = append(metas, RefMeta{Code: c, Name: "Province " + c, Valid: true})
	}
	return d, metas
}

// One award, whole log — what GetAward(code) does when the Awards panel opens.
func BenchmarkComputeOneAward(b *testing.B) {
	d, metas := benchWAIP()
	for _, n := range []int{10_000, 50_000, 100_000} {
		log := benchLog(n)
		b.Run(fmt.Sprintf("WAIP/%dqso", n), func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				Compute([]Def{d}, log, map[string][]RefMeta{"WAIP": metas}, nil)
			}
		})
	}
}

// Every shipped award at once — GetAwards(), the heaviest thing the app can ask.
func BenchmarkComputeWholeCatalog(b *testing.B) {
	defs := Defaults()
	metas := map[string][]RefMeta{}
	wd, wm := benchWAIP()
	defs = append(defs, wd)
	metas["WAIP"] = wm
	for _, c := range Catalog() {
		if raw, ok := CatalogRefs(c.Def.Code); ok {
			var refs []struct {
				Code  string `json:"code"`
				Name  string `json:"name"`
				Valid bool   `json:"valid"`
			}
			if json.Unmarshal(raw, &refs) == nil {
				m := make([]RefMeta, 0, len(refs))
				for _, r := range refs {
					m = append(m, RefMeta{Code: r.Code, Name: r.Name, Valid: r.Valid})
				}
				metas[c.Def.Code] = m
			}
		}
	}
	for _, n := range []int{10_000, 100_000} {
		log := benchLog(n)
		b.Run(fmt.Sprintf("%dawards/%dqso", len(defs), n), func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				Compute(defs, log, metas, nil)
			}
		})
	}
}
