package plugin

import (
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"strconv"
	"time"

	"gogobee/internal/db"
)

// ── FRED API Integration ───────────────────────────────────────────────────
//
// Pulls the 5/1 ARM rate weekly from FRED (series: MORTGAGE5US).
// Requires FRED_API_KEY environment variable.
// Falls back to a hardcoded default if the API is unavailable.

const fredSeries = "MORTGAGE5US"
const defaultMortgageRate = 6.5
const currentRateCacheKey = "mortgage_current_rate"

type fredResponse struct {
	Observations []struct {
		Date  string `json:"date"`
		Value string `json:"value"`
	} `json:"observations"`
}

// fetchFREDRate pulls the latest 5/1 ARM rate from the FRED API.
func fetchFREDRate() (float64, error) {
	apiKey := os.Getenv("FRED_API_KEY")
	if apiKey == "" {
		return 0, fmt.Errorf("FRED_API_KEY not set")
	}

	url := fmt.Sprintf(
		"https://api.stlouisfed.org/fred/series/observations?series_id=%s&api_key=%s&file_type=json&sort_order=desc&limit=1",
		fredSeries, apiKey,
	)

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Get(url)
	if err != nil {
		return 0, fmt.Errorf("FRED API request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return 0, fmt.Errorf("FRED API status %d", resp.StatusCode)
	}

	var data fredResponse
	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
		return 0, fmt.Errorf("FRED JSON decode: %w", err)
	}

	if len(data.Observations) == 0 {
		return 0, fmt.Errorf("FRED: no observations returned")
	}

	val := data.Observations[0].Value
	if val == "." {
		return 0, fmt.Errorf("FRED: value is placeholder")
	}

	rate, err := strconv.ParseFloat(val, 64)
	if err != nil {
		return 0, fmt.Errorf("FRED: parse rate %q: %w", val, err)
	}

	// Clamp to sane range
	if rate < 1.0 {
		rate = 1.0
	}
	if rate > 15.0 {
		rate = 15.0
	}

	return rate, nil
}

// getCurrentMortgageRate returns the cached rate, or fetches fresh if stale (daily).
func getCurrentMortgageRate() float64 {
	// Check DB cache (24h TTL)
	if val := db.CacheGet(currentRateCacheKey, 24*3600); val != "" {
		if rate, err := strconv.ParseFloat(val, 64); err == nil {
			return rate
		}
	}

	rate, err := fetchFREDRate()
	if err != nil {
		slog.Warn("mortgage: FRED fetch failed, using default", "err", err)
		return defaultMortgageRate
	}

	db.CacheSet(currentRateCacheKey, strconv.FormatFloat(rate, 'f', 2, 64))
	return rate
}

// ── Weekly Mortgage Ticker ─────────────────────────────────────────────────

func (p *AdventurePlugin) mortgageTicker() {
	ticker := time.NewTicker(1 * time.Hour)
	defer ticker.Stop()

	for {
		select {
		case <-p.stopCh:
			return
		case <-ticker.C:
			now := time.Now().UTC()
			// Run on Mondays only (FRED updates Thursdays, we process Mondays)
			if now.Weekday() != time.Monday {
				continue
			}

			_, week := now.ISOWeek()
			dateKey := fmt.Sprintf("%d-W%02d", now.Year(), week)
			jobName := "mortgage_weekly"
			if db.JobCompleted(jobName, dateKey) {
				continue
			}

			slog.Info("mortgage: running weekly payment processing")

			// Fetch fresh rate and compare with previous
			newRate := getCurrentMortgageRate()
			oldRate := p.getLastKnownRate()

			if oldRate > 0 && oldRate != newRate {
				p.sendMortgageRateChangeDMs(oldRate, newRate)
			}
			p.setLastKnownRate(newRate)

			// Process payments
			p.processMortgagePayments()

			db.MarkJobCompleted(jobName, dateKey)
		}
	}
}

const lastKnownRateCacheKey = "mortgage_last_known_rate"

func (p *AdventurePlugin) getLastKnownRate() float64 {
	val := db.CacheGet(lastKnownRateCacheKey, 365*24*3600) // 1 year TTL
	if val == "" {
		return 0
	}
	rate, err := strconv.ParseFloat(val, 64)
	if err != nil {
		return 0
	}
	return rate
}

func (p *AdventurePlugin) setLastKnownRate(rate float64) {
	db.CacheSet(lastKnownRateCacheKey, strconv.FormatFloat(rate, 'f', 2, 64))
}
