package lookup

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

// HamQTH is a lookup.Provider for hamqth.com (free with registration).
type HamQTH struct {
	User     string
	Password string

	HTTP *http.Client

	mu       sync.Mutex
	session  string
	loggedAt time.Time
}

func NewHamQTH(user, password string) *HamQTH {
	return &HamQTH{
		User:     user,
		Password: password,
		HTTP:     &http.Client{Timeout: 12 * time.Second},
	}
}

func (h *HamQTH) Name() string { return "hamqth" }

func (h *HamQTH) Lookup(ctx context.Context, callsign string) (Result, error) {
	if h.User == "" || h.Password == "" {
		return Result{}, fmt.Errorf("hamqth: credentials not set")
	}
	id, err := h.sessionID(ctx)
	if err != nil {
		return Result{}, err
	}
	r, err := h.fetch(ctx, id, callsign)
	if err == errHamQTHSessionExpired {
		h.mu.Lock()
		h.session = ""
		h.mu.Unlock()
		id, err = h.sessionID(ctx)
		if err != nil {
			return Result{}, err
		}
		r, err = h.fetch(ctx, id, callsign)
	}
	return r, err
}

func (h *HamQTH) sessionID(ctx context.Context) (string, error) {
	h.mu.Lock()
	defer h.mu.Unlock()
	// HamQTH sessions stay valid ~1h; re-login after 30min defensively.
	if h.session != "" && time.Since(h.loggedAt) < 30*time.Minute {
		return h.session, nil
	}
	u := fmt.Sprintf("https://www.hamqth.com/xml.php?u=%s&p=%s",
		url.QueryEscape(h.User), url.QueryEscape(h.Password))
	body, err := h.get(ctx, u)
	if err != nil {
		return "", err
	}
	var resp hamqthRoot
	if err := xml.Unmarshal(body, &resp); err != nil {
		return "", fmt.Errorf("hamqth: parse session: %w", err)
	}
	if resp.Session.Error != "" {
		return "", fmt.Errorf("hamqth login: %s", resp.Session.Error)
	}
	if resp.Session.ID == "" {
		return "", fmt.Errorf("hamqth: empty session id")
	}
	h.session = resp.Session.ID
	h.loggedAt = time.Now()
	return h.session, nil
}

var errHamQTHSessionExpired = fmt.Errorf("hamqth: session expired")

func (h *HamQTH) fetch(ctx context.Context, sessionID, callsign string) (Result, error) {
	u := fmt.Sprintf("https://www.hamqth.com/xml.php?id=%s&callsign=%s&prg=HamLog",
		url.QueryEscape(sessionID), url.QueryEscape(callsign))
	body, err := h.get(ctx, u)
	if err != nil {
		return Result{}, err
	}
	var resp hamqthRoot
	if err := xml.Unmarshal(body, &resp); err != nil {
		return Result{}, fmt.Errorf("hamqth: parse callsign: %w", err)
	}
	if resp.Session.Error != "" {
		msg := strings.ToLower(resp.Session.Error)
		if strings.Contains(msg, "session") || strings.Contains(msg, "expired") {
			return Result{}, errHamQTHSessionExpired
		}
		if strings.Contains(msg, "not found") || strings.Contains(msg, "callsign") {
			return Result{}, ErrNotFound
		}
		return Result{}, fmt.Errorf("hamqth: %s", resp.Session.Error)
	}
	s := resp.Search
	if s.Callsign == "" {
		return Result{}, ErrNotFound
	}
	r := Result{
		Callsign:  strings.ToUpper(s.Callsign),
		Name:      strings.TrimSpace(s.Nick + " " + s.LastName),
		QTH:       firstNonEmpty(s.QTH, s.AdrCity),
		Address:   s.AdrStreet1,
		State:     strings.ToUpper(s.USState),
		County:    s.USCounty,
		Country:   firstNonEmpty(s.AdrCountry, s.Country),
		Grid:      strings.ToUpper(s.Grid),
		Continent: strings.ToUpper(s.Continent),
		Email:     s.Email,
		QSLVia:    s.QSLVia,
	}
	r.Lat, _ = strconv.ParseFloat(s.Latitude, 64)
	r.Lon, _ = strconv.ParseFloat(s.Longitude, 64)
	r.DXCC, _ = strconv.Atoi(s.DXCC)
	r.CQZ, _ = strconv.Atoi(s.CQ)
	r.ITUZ, _ = strconv.Atoi(s.ITU)
	return r, nil
}

func (h *HamQTH) get(ctx context.Context, u string) ([]byte, error) {
	req, err := http.NewRequestWithContext(ctx, "GET", u, nil)
	if err != nil {
		return nil, err
	}
	resp, err := h.HTTP.Do(req)
	if err != nil {
		return nil, fmt.Errorf("hamqth http: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("hamqth http %d", resp.StatusCode)
	}
	return io.ReadAll(resp.Body)
}

// ----- XML shapes -----

type hamqthRoot struct {
	XMLName xml.Name      `xml:"HamQTH"`
	Session hamqthSession `xml:"session"`
	Search  hamqthSearch  `xml:"search"`
}

type hamqthSession struct {
	ID    string `xml:"session_id"`
	Error string `xml:"error"`
}

type hamqthSearch struct {
	Callsign   string `xml:"callsign"`
	Nick       string `xml:"nick"`
	LastName   string `xml:"name"`
	QTH        string `xml:"qth"`
	AdrStreet1 string `xml:"adr_street1"`
	AdrCity    string `xml:"adr_city"`
	Country    string `xml:"country"`
	AdrCountry string `xml:"adr_country"`
	USState    string `xml:"us_state"`
	USCounty   string `xml:"us_county"`
	Grid       string `xml:"grid"`
	Latitude   string `xml:"latitude"`
	Longitude  string `xml:"longitude"`
	DXCC       string `xml:"adif"` // HamQTH exposes the ADIF/DXCC number under <adif>
	CQ         string `xml:"cq"`
	ITU        string `xml:"itu"`
	Continent  string `xml:"continent"`
	Email      string `xml:"email"`
	QSLVia     string `xml:"qsl_via"`
}
