package api

import (
	"caltrack/ai"
	"caltrack/calc"
	"caltrack/config"
	"caltrack/db"
	"caltrack/types"
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"time"
)

type Handler struct {
	db  *db.DB
	ai  *Client
	cfg *types.Config
}

type Client = ai.Client

func New(database *db.DB, aiClient *ai.Client, cfg *types.Config) *Handler {
	return &Handler{
		db:  database,
		ai:  aiClient,
		cfg: cfg,
	}
}

func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
	mux.HandleFunc("GET /api/today", h.handleGetToday)
	mux.HandleFunc("GET /api/day/{date}", h.handleGetDay)
	mux.HandleFunc("POST /api/entry", h.handleAddEntry)
	mux.HandleFunc("DELETE /api/entry/{id}", h.handleDeleteEntry)
	mux.HandleFunc("DELETE /api/day/{date}", h.handleClearDay)
	mux.HandleFunc("POST /api/weighin", h.handleWeighIn)
	mux.HandleFunc("GET /api/autocomplete", h.handleAutocomplete)
	mux.HandleFunc("GET /api/history", h.handleHistory)
	mux.HandleFunc("GET /api/weight-history", h.handleWeightHistory)
	mux.HandleFunc("GET /api/settings", h.handleGetSettings)
	mux.HandleFunc("POST /api/settings", h.handleUpdateSettings)
}

func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(v)
}

func writeError(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, status, map[string]string{"error": msg})
}

func (h *Handler) handleGetToday(w http.ResponseWriter, r *http.Request) {
	h.handleGetDayData(w, time.Now().Format("2006-01-02"))
}

func (h *Handler) handleGetDay(w http.ResponseWriter, r *http.Request) {
	date := r.PathValue("date")
	if date == "" {
		writeError(w, http.StatusBadRequest, "date is required")
		return
	}
	h.handleGetDayData(w, date)
}

func (h *Handler) handleGetDayData(w http.ResponseWriter, date string) {
	entries, err := h.db.GetEntriesByDate(date)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	if entries == nil {
		entries = []types.Entry{}
	}

	targetKcal := calc.DailyTarget(*h.cfg)
	summary, err := h.db.GetDailySummary(date, targetKcal)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}

	weight, _ := h.db.GetLatestWeight()
	if weight == 0 {
		weight = h.cfg.CurrentWeightKg
	}

	h.cfg.HasAPIKey = h.cfg.AnthropicAPIKey != ""
	writeJSON(w, http.StatusOK, types.TodayResponse{
		Date:       date,
		Entries:    entries,
		Summary:    summary,
		TargetKcal: targetKcal,
		Deficit:    calc.Deficit(*h.cfg),
		Weight:     weight,
		Config:     *h.cfg,
	})
}

func (h *Handler) handleAddEntry(w http.ResponseWriter, r *http.Request) {
	var req types.AddEntryRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	if req.Description == "" {
		writeError(w, http.StatusBadRequest, "description is required")
		return
	}

	if req.Type == "" {
		req.Type = types.EntryFood
	}

	// Use AI to estimate calories
	estimation, err := h.ai.EstimateCalories(r.Context(), req.Description, req.Type)
	if err != nil {
		log.Printf("AI estimation error: %v", err)
		writeError(w, http.StatusInternalServerError, "failed to estimate calories: "+err.Error())
		return
	}

	var entries []types.Entry
	for _, item := range estimation.Items {
		desc := item.Name
		if item.Brand != "" {
			desc = item.Name + " (" + item.Brand + ")"
		}

		entry := types.Entry{
			Type:        req.Type,
			Description: desc,
			Kcal:        item.Kcal,
		}

		entry, err = h.db.AddEntry(entry)
		if err != nil {
			writeError(w, http.StatusInternalServerError, err.Error())
			return
		}
		entries = append(entries, entry)
	}

	writeJSON(w, http.StatusCreated, types.AddEntryResponse{
		Entries: entries,
	})
}

func (h *Handler) handleDeleteEntry(w http.ResponseWriter, r *http.Request) {
	idStr := r.PathValue("id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid entry id")
		return
	}

	if err := h.db.DeleteEntry(id); err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
}

func (h *Handler) handleClearDay(w http.ResponseWriter, r *http.Request) {
	date := r.PathValue("date")
	if date == "" {
		writeError(w, http.StatusBadRequest, "date is required")
		return
	}

	if err := h.db.ClearDay(date); err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]string{"status": "cleared"})
}

func (h *Handler) handleWeighIn(w http.ResponseWriter, r *http.Request) {
	var req types.WeighInRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	if req.WeightKg <= 0 {
		writeError(w, http.StatusBadRequest, "weight must be positive")
		return
	}

	weighIn, err := h.db.AddWeighIn("", req.WeightKg)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}

	// Update config with new weight
	h.cfg.CurrentWeightKg = req.WeightKg
	config.Save(*h.cfg)

	writeJSON(w, http.StatusCreated, weighIn)
}

func (h *Handler) handleAutocomplete(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query().Get("q")
	if len(query) < 2 {
		writeJSON(w, http.StatusOK, []types.AutocompleteResult{})
		return
	}

	results, err := h.db.SearchProducts(query)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	if results == nil {
		results = []types.AutocompleteResult{}
	}

	writeJSON(w, http.StatusOK, results)
}

func (h *Handler) handleHistory(w http.ResponseWriter, r *http.Request) {
	summaries, err := h.db.GetHistory(30)
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	if summaries == nil {
		summaries = []types.DailySummary{}
	}

	writeJSON(w, http.StatusOK, summaries)
}

func (h *Handler) handleWeightHistory(w http.ResponseWriter, r *http.Request) {
	weighIns, err := h.db.GetWeightHistory()
	if err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}
	if weighIns == nil {
		weighIns = []types.WeighIn{}
	}

	writeJSON(w, http.StatusOK, weighIns)
}

func (h *Handler) handleGetSettings(w http.ResponseWriter, r *http.Request) {
	h.cfg.HasAPIKey = h.cfg.AnthropicAPIKey != ""
	writeJSON(w, http.StatusOK, h.cfg)
}

func (h *Handler) handleUpdateSettings(w http.ResponseWriter, r *http.Request) {
	var req types.SettingsRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	if req.CurrentWeightKg != nil {
		h.cfg.CurrentWeightKg = *req.CurrentWeightKg
	}
	if req.TargetWeightKg != nil {
		h.cfg.TargetWeightKg = *req.TargetWeightKg
	}
	if req.HeightCm != nil {
		h.cfg.HeightCm = *req.HeightCm
	}
	if req.Age != nil {
		h.cfg.Age = *req.Age
	}
	if req.Sex != nil {
		h.cfg.Sex = *req.Sex
	}
	if req.ActivityLevel != nil {
		h.cfg.ActivityLevel = *req.ActivityLevel
	}
	if req.WeeklyLossKg != nil {
		h.cfg.WeeklyLossKg = *req.WeeklyLossKg
	}
	if req.AnthropicAPIKey != nil {
		h.cfg.AnthropicAPIKey = *req.AnthropicAPIKey
	}

	if err := config.Save(*h.cfg); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to save config: "+err.Error())
		return
	}

	h.cfg.HasAPIKey = h.cfg.AnthropicAPIKey != ""
	writeJSON(w, http.StatusOK, h.cfg)
}
