package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"drivervault/apiserver/internal/models"
)

// Technical check history: the mandatory roadworthiness inspections a car has
// been through — przegląd techniczny, MOT, TÜV.
//
// Shaped like the service log next to it, but time-only: an inspection falls due
// on a date regardless of the odometer. The next-due date is assessed live on
// every read (models.TechnicalCheck.ComputeTechnicalCheckDerived) rather than
// stored, for the same reason documents are — a lapsed certificate is a car that
// cannot legally be driven, and a stored verdict would quietly go stale.
//
// The certificate scan is handled by attachments.go, on the same terms as every
// other record's attachment.

var technicalCheckResults = map[string]bool{"passed": true, "failed": true}

// listCarTechnicalChecks serves GET /api/cars/{id}/technical-checks.
func (s *Server) listCarTechnicalChecks(w http.ResponseWriter, r *http.Request) {
	carID := r.PathValue("id")
	if !s.requireCarAccess(w, r, carID, accessRead) {
		return
	}
	s.respondTechnicalCheckList(w, r, carID)
}

// listTechnicalChecks serves GET /api/technical-checks?car={id}.
func (s *Server) listTechnicalChecks(w http.ResponseWriter, r *http.Request) {
	carID := r.URL.Query().Get("car")
	if !s.requireCarAccess(w, r, carID, accessRead) {
		return
	}
	s.respondTechnicalCheckList(w, r, carID)
}

func (s *Server) respondTechnicalCheckList(w http.ResponseWriter, r *http.Request, carID string) {
	q := url.Values{}
	q.Set("sort", "-date") // most-recent check first, like the service log
	q.Set("perPage", "500")
	q.Set("filter", fmt.Sprintf("car='%s'", carID))

	res, err := s.pb.List(r.Context(), colTechnicalChecks, q)
	if err != nil {
		writePBError(w, err)
		return
	}
	var recs []technicalCheckRecord
	if err := json.Unmarshal(res.Items, &recs); err != nil {
		writeError(w, http.StatusInternalServerError, err.Error())
		return
	}

	cars := newCarCache(s)
	now := time.Now()
	out := make([]models.TechnicalCheck, 0, len(recs))
	for _, rec := range recs {
		m := rec.toModel()
		car, err := cars.get(r.Context(), m.Car)
		if err != nil {
			car = nil // fall back to the default interval rather than dropping the row
		}
		m.ComputeTechnicalCheckDerived(car, now)
		out = append(out, m)
	}
	writeJSON(w, http.StatusOK, out)
}

func (s *Server) getTechnicalCheck(w http.ResponseWriter, r *http.Request) {
	var rec technicalCheckRecord
	if err := s.pb.GetOne(r.Context(), colTechnicalChecks, r.PathValue("id"), &rec); err != nil {
		writePBError(w, err)
		return
	}
	m := rec.toModel()
	if !s.requireCarAccess(w, r, m.Car, accessRead) {
		return
	}
	s.writeTechnicalCheck(w, r, m, http.StatusOK)
}

func (s *Server) createTechnicalCheck(w http.ResponseWriter, r *http.Request) {
	var in models.TechnicalCheck
	if err := decodeJSON(r, &in); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	if in.Result == "" {
		in.Result = "passed"
	}
	if msg := validateTechnicalCheck(in); msg != "" {
		writeError(w, http.StatusBadRequest, msg)
		return
	}
	if !s.requireCarAccess(w, r, in.Car, accessWrite) {
		return
	}

	var rec technicalCheckRecord
	if err := s.pb.Create(r.Context(), colTechnicalChecks, technicalCheckPayload(in), &rec); err != nil {
		writePBError(w, err)
		return
	}
	s.writeTechnicalCheck(w, r, rec.toModel(), http.StatusCreated)
}

func (s *Server) updateTechnicalCheck(w http.ResponseWriter, r *http.Request) {
	var in models.TechnicalCheck
	if err := decodeJSON(r, &in); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	// Enforce write access via the record's existing parent car, so the body's
	// car field cannot be used to escape into another user's car.
	var existing technicalCheckRecord
	if err := s.pb.GetOne(r.Context(), colTechnicalChecks, r.PathValue("id"), &existing); err != nil {
		writePBError(w, err)
		return
	}
	if !s.requireCarAccess(w, r, existing.Car, accessWrite) {
		return
	}
	in.Car = existing.Car // a check's car is not reassignable via PATCH
	if in.Result == "" {
		in.Result = "passed"
	}
	if msg := validateTechnicalCheck(in); msg != "" {
		writeError(w, http.StatusBadRequest, msg)
		return
	}

	var rec technicalCheckRecord
	if err := s.pb.Update(r.Context(), colTechnicalChecks, r.PathValue("id"), technicalCheckPayload(in), &rec); err != nil {
		writePBError(w, err)
		return
	}
	s.writeTechnicalCheck(w, r, rec.toModel(), http.StatusOK)
}

func (s *Server) deleteTechnicalCheck(w http.ResponseWriter, r *http.Request) {
	var existing technicalCheckRecord
	if err := s.pb.GetOne(r.Context(), colTechnicalChecks, 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(), colTechnicalChecks, r.PathValue("id")); err != nil {
		writePBError(w, err)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// writeTechnicalCheck derives the next-due date against the parent car and
// responds. Shared by the three handlers that return a single check.
func (s *Server) writeTechnicalCheck(w http.ResponseWriter, r *http.Request, m models.TechnicalCheck, status int) {
	car, err := s.carModel(r.Context(), m.Car)
	if err != nil {
		car = nil
	}
	m.ComputeTechnicalCheckDerived(car, time.Now())
	writeJSON(w, status, m)
}

func validateTechnicalCheck(t models.TechnicalCheck) string {
	switch {
	case t.Car == "":
		return "car is required"
	case t.Date.IsZero():
		return "date is required"
	case !technicalCheckResults[t.Result]:
		return "result must be passed or failed"
	case t.Cost < 0:
		return "cost cannot be negative"
	case len(strings.TrimSpace(t.Station)) > 200:
		return "station name is too long"
	}
	if t.ValidUntil != nil && !t.ValidUntil.IsZero() && t.ValidUntil.Before(t.Date) {
		return "valid-until date cannot be before the check date"
	}
	return ""
}
