package awardref

import (
	"context"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"
)

// Importer downloads and parses a program's reference list into []Ref.
type Importer struct {
	AwardCode string
	URL       string
	Fetch     func(ctx context.Context, body io.Reader) ([]Ref, error)
}

// Importers is the registry of built-in reference-list updaters, keyed by
// award code. Awards not present here have no online list (manual only).
var Importers = map[string]Importer{
	"POTA": {AwardCode: "POTA", URL: "https://pota.app/all_parks.csv", Fetch: parsePOTA},
	"SOTA": {AwardCode: "SOTA", URL: "https://www.sotadata.org.uk/summitslist.csv", Fetch: parseSOTA},
	"WWFF": {AwardCode: "WWFF", URL: "https://wwff.co/wwff-data/wwff_directory.csv", Fetch: parseWWFF},
	"IOTA": {AwardCode: "IOTA", URL: "https://www.iota-world.org/islands-on-the-air/downloads/download-file.html?path=groups.json", Fetch: parseIOTA},
}

// parseIOTA reads iota-world.org's groups.json (refreshed daily): an array of
// {refno, name, dxcc_num, grp_region}. The reference is the IOTA number
// (EU-005); the DXCC number lets the per-QSO picker filter by entity.
func parseIOTA(_ context.Context, body io.Reader) ([]Ref, error) {
	var groups []struct {
		RefNo  string `json:"refno"`
		Name   string `json:"name"`
		DXCC   string `json:"dxcc_num"`
		Region string `json:"grp_region"`
	}
	if err := json.NewDecoder(body).Decode(&groups); err != nil {
		return nil, fmt.Errorf("parse IOTA json: %w", err)
	}
	out := make([]Ref, 0, len(groups))
	for _, g := range groups {
		ref := strings.ToUpper(strings.TrimSpace(g.RefNo))
		if ref == "" {
			continue
		}
		dxcc, _ := strconv.Atoi(strings.TrimSpace(g.DXCC))
		grp := strings.TrimSpace(g.Region)
		if grp == "" { // fall back to the continent prefix (AF/EU/NA/…)
			if i := strings.IndexByte(ref, '-'); i > 0 {
				grp = ref[:i]
			}
		}
		out = append(out, Ref{Code: ref, Name: strings.TrimSpace(g.Name), DXCC: dxcc, Group: grp})
	}
	return out, nil
}

// CanUpdate reports whether an award has an online reference list.
func CanUpdate(awardCode string) bool {
	_, ok := Importers[strings.ToUpper(strings.TrimSpace(awardCode))]
	return ok
}

// Download fetches and parses the reference list for an award (does not store).
func Download(ctx context.Context, awardCode string) ([]Ref, error) {
	imp, ok := Importers[strings.ToUpper(strings.TrimSpace(awardCode))]
	if !ok {
		return nil, fmt.Errorf("no online list for award %q", awardCode)
	}
	req, err := http.NewRequestWithContext(ctx, "GET", imp.URL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", "OpsLog")
	client := &http.Client{Timeout: 5 * time.Minute}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("download %s: %w", imp.URL, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("download %s: http %d", imp.URL, resp.StatusCode)
	}
	return imp.Fetch(ctx, resp.Body)
}

// headerIndex maps lowercased header names to their column index.
func headerIndex(header []string) map[string]int {
	m := make(map[string]int, len(header))
	for i, h := range header {
		m[strings.ToLower(strings.TrimSpace(h))] = i
	}
	return m
}

func get(rec []string, idx int) string {
	if idx < 0 || idx >= len(rec) {
		return ""
	}
	return strings.TrimSpace(rec[idx])
}

// parsePOTA: "reference","name","active","entityId","locationDesc"
func parsePOTA(_ context.Context, body io.Reader) ([]Ref, error) {
	r := csv.NewReader(body)
	r.FieldsPerRecord = -1
	header, err := r.Read()
	if err != nil {
		return nil, err
	}
	h := headerIndex(header)
	iRef, iName, iActive, iEnt, iLoc := h["reference"], h["name"], h["active"], h["entityid"], h["locationdesc"]
	var out []Ref
	for {
		rec, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			continue
		}
		if iActive >= 0 && get(rec, iActive) == "0" {
			continue
		}
		dxcc, _ := strconv.Atoi(get(rec, iEnt))
		out = append(out, Ref{Code: get(rec, iRef), Name: get(rec, iName), DXCC: dxcc, Group: get(rec, iLoc)})
	}
	return out, nil
}

// parseSOTA: first line is a title, then header
// SummitCode,AssociationName,RegionName,SummitName,…
func parseSOTA(_ context.Context, body io.Reader) ([]Ref, error) {
	r := csv.NewReader(body)
	r.FieldsPerRecord = -1
	// First record is the "SOTA Summits List (Date=…)" title line — skip it.
	if _, err := r.Read(); err != nil {
		return nil, err
	}
	header, err := r.Read()
	if err != nil {
		return nil, err
	}
	h := headerIndex(header)
	iRef, iName, iAssoc, iRegion := h["summitcode"], h["summitname"], h["associationname"], h["regionname"]
	var out []Ref
	for {
		rec, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			continue
		}
		out = append(out, Ref{Code: get(rec, iRef), Name: get(rec, iName), Group: get(rec, iAssoc), SubGrp: get(rec, iRegion)})
	}
	return out, nil
}

// parseWWFF: reference,status,name,…,dxccEnum,… (header-driven)
func parseWWFF(_ context.Context, body io.Reader) ([]Ref, error) {
	r := csv.NewReader(body)
	r.FieldsPerRecord = -1
	header, err := r.Read()
	if err != nil {
		return nil, err
	}
	h := headerIndex(header)
	iRef, iName, iStatus, iCountry := h["reference"], h["name"], h["status"], h["country"]
	iDXCC := h["dxccenum"]
	if iDXCC < 0 {
		iDXCC = h["dxcc"]
	}
	var out []Ref
	for {
		rec, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			continue
		}
		if iStatus >= 0 && !strings.EqualFold(get(rec, iStatus), "active") {
			continue
		}
		dxcc, _ := strconv.Atoi(get(rec, iDXCC))
		out = append(out, Ref{Code: get(rec, iRef), Name: get(rec, iName), DXCC: dxcc, Group: get(rec, iCountry)})
	}
	return out, nil
}
