package extsvc

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

// Club Log's log-matching API. A "match" is a QSO that BOTH stations uploaded
// to Club Log, paired within ±15 minutes — Club Log's own equivalent of a LoTW
// confirmation. getmatches.php returns a JSON array of 5-element arrays:
//
//	[["G0LGJ/M","223","2005-07-16 08:00:00","20","CW"], …]
//	  callsign   dxcc   qso datetime (UTC)   band  mode ("false" when unknown)
//
// The optional start date filters on when Club Log COMPLETED the match (not
// the QSO date), which is exactly what an incremental "since last download"
// pull wants.
const clublogMatchesURL = "https://clublog.org/getmatches.php"

// ClublogMatch is one confirmed pairing from getmatches.php.
type ClublogMatch struct {
	Callsign string
	DXCC     int
	When     time.Time
	Band     string // ADIF band ("20m", "70cm"); "" if the id is unknown
	Mode     string // "" when Club Log doesn't know it
}

// clublogBandNames maps Club Log's numeric band ids to ADIF band names. The
// ids are the wavelength number; the only trap is that ids past the metre
// bands are centimetres (the docs' own example: 70 = 70CM).
var clublogBandNames = map[string]string{
	"2200": "2200m", "630": "630m", "160": "160m", "80": "80m", "60": "60m",
	"40": "40m", "30": "30m", "20": "20m", "17": "17m", "15": "15m",
	"12": "12m", "10": "10m", "8": "8m", "6": "6m", "5": "5m", "4": "4m",
	"2": "2m", "70": "70cm", "23": "23cm", "13": "13cm", "9": "9cm", "3": "3cm",
}

// DownloadClublogMatches pulls the account's log matches for cfg.Callsign,
// optionally only those Club Log completed since sinceDate ("2006-01-02").
func DownloadClublogMatches(ctx context.Context, client *http.Client, cfg ServiceConfig, sinceDate string) ([]ClublogMatch, error) {
	email := strings.TrimSpace(cfg.Email)
	call := strings.ToUpper(strings.TrimSpace(cfg.Callsign))
	switch {
	case email == "":
		return nil, fmt.Errorf("clublog: account email not set")
	case cfg.Password == "":
		return nil, fmt.Errorf("clublog: password not set")
	case call == "":
		return nil, fmt.Errorf("clublog: callsign not set")
	}
	v := url.Values{}
	v.Set("api", clublogAppAPIKey)
	v.Set("email", email)
	v.Set("password", cfg.Password)
	v.Set("callsign", call)
	if t, err := time.Parse("2006-01-02", strings.TrimSpace(sinceDate)); err == nil {
		v.Set("startyear", strconv.Itoa(t.Year()))
		v.Set("startmonth", strconv.Itoa(int(t.Month())))
		v.Set("startday", strconv.Itoa(t.Day()))
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, clublogMatchesURL+"?"+v.Encode(), nil)
	if err != nil {
		return nil, err
	}
	if client == nil {
		client = &http.Client{Timeout: 120 * time.Second}
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 64<<20))
	text := strings.TrimSpace(string(body))
	if resp.StatusCode != http.StatusOK {
		if looksLikeHTML(text) || len(text) > 300 {
			return nil, fmt.Errorf("clublog: HTTP %d", resp.StatusCode)
		}
		return nil, fmt.Errorf("clublog: HTTP %d: %s", resp.StatusCode, text)
	}
	if looksLikeHTML(text) {
		return nil, fmt.Errorf("clublog: got a web page instead of matches — check email/password/callsign")
	}
	var raw [][]any
	if err := json.Unmarshal([]byte(text), &raw); err != nil {
		return nil, fmt.Errorf("clublog: bad matches JSON: %w", err)
	}
	str := func(x any) string {
		switch t := x.(type) {
		case string:
			return t
		case float64:
			return strconv.FormatFloat(t, 'f', -1, 64)
		default:
			return ""
		}
	}
	out := make([]ClublogMatch, 0, len(raw))
	for _, rec := range raw {
		if len(rec) < 5 {
			continue
		}
		m := ClublogMatch{Callsign: strings.ToUpper(strings.TrimSpace(str(rec[0])))}
		m.DXCC, _ = strconv.Atoi(str(rec[1]))
		if t, err := time.Parse("2006-01-02 15:04:05", str(rec[2])); err == nil {
			m.When = t.UTC()
		}
		m.Band = clublogBandNames[strings.TrimSpace(str(rec[3]))]
		if md := strings.TrimSpace(str(rec[4])); md != "" && !strings.EqualFold(md, "false") {
			m.Mode = strings.ToUpper(md)
		}
		if m.Callsign == "" || m.When.IsZero() {
			continue
		}
		out = append(out, m)
	}
	return out, nil
}
