package main

import (
	"fmt"
	"strings"
	"time"

	"hamlog/internal/applog"
)

// Live operator status — for multi-operator events on a SHARED MySQL logbook
// (e.g. a special-event call like TM74FR with several ops on different bands).
// Each OpsLog instance heartbeats its current activity (operator call + station
// call + freq/band/mode from CAT) into a `live_status` table every ~15s. A tiny
// web script on the operator's own server reads that table and renders a live
// page/image that the QRZ.com bio can embed (`<img src=…>`). OpsLog only WRITES
// to the DB — it is not a web server. Rows older than a couple of minutes are
// "stale" (operator went offline); the web side ignores them.

const keyLiveStatusEnabled = "livestatus.enabled"

// GetLiveStatusEnabled reports whether this operator publishes live status.
func (a *App) GetLiveStatusEnabled() bool {
	if a.settings == nil {
		return false
	}
	v, _ := a.settings.Get(a.ctx, keyLiveStatusEnabled)
	return strings.TrimSpace(v) == "1"
}

// SetLiveStatusEnabled turns live-status publishing on or off (off also removes
// this operator's row immediately).
func (a *App) SetLiveStatusEnabled(on bool) error {
	if a.settings == nil {
		return fmt.Errorf("db not initialized")
	}
	val := "0"
	if on {
		val = "1"
	}
	if err := a.settings.Set(a.ctx, keyLiveStatusEnabled, val); err != nil {
		return err
	}
	if on {
		applog.Printf("livestatus: enabled (logbook backend=%q, mysql conn=%v)", a.dbBackend, a.logDb != nil)
		go a.publishLiveStatus() // show up right away
	} else {
		a.clearLiveStatus()
	}
	return nil
}

// liveStatusLoop heartbeats the current activity while enabled. Started once at
// startup; cheap no-op when disabled or not on MySQL.
func (a *App) liveStatusLoop() {
	defer func() { _ = recover() }() // never crash the app from here
	applog.Printf("livestatus: loop started")
	a.publishLiveStatus() // attempt immediately, don't wait the first tick
	t := time.NewTicker(15 * time.Second)
	defer t.Stop()
	for range t.C {
		a.publishLiveStatus()
	}
}

// liveStatusActive reports whether publishing should run (MySQL logbook + on).
func (a *App) liveStatusActive() bool {
	return a.logDb != nil && a.dbBackend == "mysql" && a.GetLiveStatusEnabled()
}

// liveStatusOperator returns this instance's operator id (the operator callsign,
// falling back to the station callsign for a single-op setup). The callsign and
// operator live on the ACTIVE PROFILE (station_profiles table), NOT in the
// settings KV — read them there.
func (a *App) liveStatusOperator() (op, station string) {
	if a.profiles == nil {
		return "", ""
	}
	p, err := a.profiles.Active(a.ctx)
	if err != nil {
		return "", ""
	}
	station = strings.ToUpper(strings.TrimSpace(p.Callsign))
	op = strings.ToUpper(strings.TrimSpace(p.Operator))
	if op == "" {
		op = station
	}
	return op, station
}

// ReportLiveActivity is called by the UI with the current entry-strip freq/band/
// mode, used as a fallback for live status when the CAT isn't connected.
func (a *App) ReportLiveActivity(freqHz int64, band, mode string) {
	a.liveActMu.Lock()
	a.liveFreqHz = freqHz
	a.liveBand = strings.ToUpper(strings.TrimSpace(band))
	a.liveMode = strings.ToUpper(strings.TrimSpace(mode))
	a.liveActMu.Unlock()
}

// publishLiveStatus upserts this operator's current activity. Best effort, with
// explicit logging so a silent no-op is diagnosable.
func (a *App) publishLiveStatus() {
	if a.logDb == nil || a.dbBackend != "mysql" {
		return // not a MySQL logbook — nothing to do (silent, runs every 15s)
	}
	if !a.GetLiveStatusEnabled() {
		return // disabled (silent)
	}
	op, station := a.liveStatusOperator()
	if op == "" {
		applog.Printf("livestatus: nothing published — no operator/callsign set (Settings → Station)")
		return
	}
	var freqHz int64
	var band, mode string
	if a.cat != nil {
		st := a.cat.State()
		if st.Connected {
			freqHz, band, mode = st.FreqHz, st.Band, st.Mode
		}
	}
	// Fall back to whatever the entry strip last reported (so band/mode/freq are
	// published even when the CAT isn't connected).
	a.liveActMu.Lock()
	if freqHz == 0 {
		freqHz = a.liveFreqHz
	}
	if band == "" {
		band = a.liveBand
	}
	if mode == "" {
		mode = a.liveMode
	}
	a.liveActMu.Unlock()
	if err := a.ensureLiveStatusTable(); err != nil {
		applog.Printf("livestatus: CREATE TABLE failed: %v", err)
		return
	}
	_, err := a.logDb.ExecContext(a.ctx,
		"INSERT INTO live_status (operator, station, freq_hz, band, mode, updated_at) "+
			"VALUES (?, ?, ?, ?, ?, UTC_TIMESTAMP()) "+
			"ON DUPLICATE KEY UPDATE station=VALUES(station), freq_hz=VALUES(freq_hz), "+
			"band=VALUES(band), mode=VALUES(mode), updated_at=UTC_TIMESTAMP()",
		op, station, freqHz, band, mode)
	if err != nil {
		applog.Printf("livestatus: INSERT failed: %v", err)
		return
	}
	applog.Printf("livestatus: published op=%s station=%s %dHz %s %s", op, station, freqHz, band, mode)
}

func (a *App) ensureLiveStatusTable() error {
	_, err := a.logDb.ExecContext(a.ctx,
		"CREATE TABLE IF NOT EXISTS live_status ("+
			"operator VARCHAR(32) PRIMARY KEY, "+
			"station VARCHAR(32), "+
			"freq_hz BIGINT, "+
			"band VARCHAR(16), "+
			"mode VARCHAR(16), "+
			"updated_at DATETIME)")
	return err
}

// clearLiveStatus removes this operator's row (on disable / shutdown).
func (a *App) clearLiveStatus() {
	if a.logDb == nil || a.dbBackend != "mysql" {
		return
	}
	op, _ := a.liveStatusOperator()
	if op == "" {
		return
	}
	_, _ = a.logDb.ExecContext(a.ctx, "DELETE FROM live_status WHERE operator=?", op)
}
