package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"sort"

	"drivervault/apiserver/internal/models"
)

// Fuel tracking: a log of refills per car, plus the efficiency derived from it.
//
// Nothing about consumption is stored — it is recomputed from the whole history
// on every read (models.ComputeFuelDerived). Editing a fill three months back
// therefore corrects every window it touches, with no rows to migrate.

// fetchFuelEntries loads a car's refills oldest-first and fills in the derived
// efficiency fields. Ordering is by odometer rather than date because the
// full-tank windows are spans of distance, and a fill logged with the wrong date
// would otherwise scramble the chain.
func (s *Server) fetchFuelEntries(r *http.Request, carID string) ([]models.FuelEntry, error) {
	res, err := s.pb.List(r.Context(), colFuel, url.Values{
		"filter":  {fmt.Sprintf("car='%s'", carID)},
		"sort":    {"km"},
		"perPage": {"1000"},
	})
	if err != nil {
		return nil, err
	}
	var recs []fuelRecord
	if err := json.Unmarshal(res.Items, &recs); err != nil {
		return nil, err
	}
	out := make([]models.FuelEntry, 0, len(recs))
	for _, rec := range recs {
		out = append(out, rec.toModel())
	}
	// PocketBase sorts numerically here, but re-sorting locally keeps the
	// invariant ComputeFuelDerived depends on explicit and cheap.
	sort.SliceStable(out, func(i, j int) bool { return out[i].Km < out[j].Km })
	models.ComputeFuelDerived(out)
	return out, nil
}

// listCarFuelEntries serves GET /api/cars/{id}/fuel-entries, newest first.
func (s *Server) listCarFuelEntries(w http.ResponseWriter, r *http.Request) {
	carID := r.PathValue("id")
	if !s.requireCarAccess(w, r, carID, accessRead) {
		return
	}
	entries, err := s.fetchFuelEntries(r, carID)
	if err != nil {
		writePBError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, reverseFuel(entries))
}

// listFuelEntries serves GET /api/fuel-entries?car={id}.
func (s *Server) listFuelEntries(w http.ResponseWriter, r *http.Request) {
	carID := r.URL.Query().Get("car")
	if !s.requireCarAccess(w, r, carID, accessRead) {
		return
	}
	entries, err := s.fetchFuelEntries(r, carID)
	if err != nil {
		writePBError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, reverseFuel(entries))
}

// listCarFuelStats serves GET /api/cars/{id}/fuel-stats.
func (s *Server) listCarFuelStats(w http.ResponseWriter, r *http.Request) {
	carID := r.PathValue("id")
	if !s.requireCarAccess(w, r, carID, accessRead) {
		return
	}
	entries, err := s.fetchFuelEntries(r, carID)
	if err != nil {
		writePBError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, models.ComputeFuelStats(entries))
}

// reverseFuel flips the oldest-first working order into the newest-first order
// clients display.
func reverseFuel(in []models.FuelEntry) []models.FuelEntry {
	out := make([]models.FuelEntry, len(in))
	for i, e := range in {
		out[len(in)-1-i] = e
	}
	return out
}

// withFuelDerived recomputes the whole history and returns the one entry the
// caller just wrote, so a create/update response carries the same derived
// figures the list would show.
func (s *Server) withFuelDerived(r *http.Request, carID, id string) (models.FuelEntry, error) {
	entries, err := s.fetchFuelEntries(r, carID)
	if err != nil {
		return models.FuelEntry{}, err
	}
	for _, e := range entries {
		if e.ID == id {
			return e, nil
		}
	}
	return models.FuelEntry{}, fmt.Errorf("fuel entry %s not found after write", id)
}

func (s *Server) getFuelEntry(w http.ResponseWriter, r *http.Request) {
	var rec fuelRecord
	if err := s.pb.GetOne(r.Context(), colFuel, r.PathValue("id"), &rec); err != nil {
		writePBError(w, err)
		return
	}
	if !s.requireCarAccess(w, r, rec.Car, accessRead) {
		return
	}
	entry, err := s.withFuelDerived(r, rec.Car, rec.ID)
	if err != nil {
		writePBError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, entry)
}

func (s *Server) createFuelEntry(w http.ResponseWriter, r *http.Request) {
	var in models.FuelEntry
	if err := decodeJSON(r, &in); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	if err := validateFuelEntry(in); err != "" {
		writeError(w, http.StatusBadRequest, err)
		return
	}
	if !s.requireCarAccess(w, r, in.Car, accessWrite) {
		return
	}
	var rec fuelRecord
	if err := s.pb.Create(r.Context(), colFuel, fuelPayload(in), &rec); err != nil {
		writePBError(w, err)
		return
	}
	// A refill is also the freshest odometer reading there is; keeping the car in
	// step means the km-based service and reminder status stay honest without the
	// user retyping the number on the car itself.
	s.advanceOdometer(r, in.Car, in.Km)

	entry, err := s.withFuelDerived(r, rec.Car, rec.ID)
	if err != nil {
		writePBError(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, entry)
}

func (s *Server) updateFuelEntry(w http.ResponseWriter, r *http.Request) {
	var in models.FuelEntry
	if err := decodeJSON(r, &in); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	var existing fuelRecord
	if err := s.pb.GetOne(r.Context(), colFuel, r.PathValue("id"), &existing); err != nil {
		writePBError(w, err)
		return
	}
	if !s.requireCarAccess(w, r, existing.Car, accessWrite) {
		return
	}
	// The car is fixed by the record being edited; a body claiming another car
	// must not move the entry across the access boundary just checked.
	in.Car = existing.Car
	if err := validateFuelEntry(in); err != "" {
		writeError(w, http.StatusBadRequest, err)
		return
	}
	var rec fuelRecord
	if err := s.pb.Update(r.Context(), colFuel, r.PathValue("id"), fuelPayload(in), &rec); err != nil {
		writePBError(w, err)
		return
	}
	s.advanceOdometer(r, rec.Car, in.Km)

	entry, err := s.withFuelDerived(r, rec.Car, rec.ID)
	if err != nil {
		writePBError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, entry)
}

func (s *Server) deleteFuelEntry(w http.ResponseWriter, r *http.Request) {
	var existing fuelRecord
	if err := s.pb.GetOne(r.Context(), colFuel, r.PathValue("id"), &existing); err != nil {
		writePBError(w, err)
		return
	}
	if !s.requireCarAccess(w, r, existing.Car, accessWrite) {
		return
	}
	if err := s.pb.Delete(r.Context(), colFuel, r.PathValue("id")); err != nil {
		writePBError(w, err)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// validateFuelEntry returns a human-readable reason the entry is unusable, or ""
// when it is fine.
func validateFuelEntry(f models.FuelEntry) string {
	switch {
	case f.Car == "":
		return "car is required"
	case f.Date.IsZero():
		return "date is required"
	case f.Km < 0:
		return "odometer (km) cannot be negative"
	case f.Liters <= 0:
		return "liters must be greater than zero"
	case f.Cost < 0:
		return "cost cannot be negative"
	}
	return ""
}

// advanceOdometer moves the car's current_km forward to km. It only ever
// increases the reading and never fails the caller's request: a stale odometer
// is a cosmetic problem, whereas rejecting a valid refill over it is not. A
// lower km means the user is backfilling older history, which must not rewind
// the car.
func (s *Server) advanceOdometer(r *http.Request, carID string, km int) {
	if km <= 0 {
		return
	}
	var car carRecord
	if err := s.pb.GetOne(r.Context(), colCars, carID, &car); err != nil {
		return
	}
	if km <= car.CurrentKm {
		return
	}
	_ = s.pb.Update(r.Context(), colCars, carID, map[string]any{"current_km": km}, nil)
}
