package api

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

	"drivervault/apiserver/internal/models"
)

// Document tracking: insurance policies, pollution/emissions certificates,
// registration papers and the like, each with a renewal date.
//
// The expiry date is the reason the feature exists — a lapsed policy is a car
// that cannot legally be driven — so it is assessed live on every read
// (models.CarDocument.ComputeExpiry) rather than stored and left to go stale.
// Documents also feed the auto-derived reminders in reminders.go.
//
// The scan/PDF attached to a document is handled by attachments.go, on the same
// terms as every other record's attachment.

var documentTypes = map[string]bool{
	"insurance":    true,
	"pollution":    true,
	"registration": true,
	"inspection":   true,
	"roadTax":      true,
	"warranty":     true,
	"other":        true,
}

// listCarDocuments serves GET /api/cars/{id}/documents.
func (s *Server) listCarDocuments(w http.ResponseWriter, r *http.Request) {
	carID := r.PathValue("id")
	if !s.requireCarAccess(w, r, carID, accessRead) {
		return
	}
	s.respondDocumentList(w, r, carID)
}

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

func (s *Server) respondDocumentList(w http.ResponseWriter, r *http.Request, carID string) {
	docs, err := s.fetchDocuments(r, carID)
	if err != nil {
		writePBError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, docs)
}

// fetchDocuments loads a car's documents with the soonest renewal first — the
// order in which they need attention.
func (s *Server) fetchDocuments(r *http.Request, carID string) ([]models.CarDocument, error) {
	res, err := s.pb.List(r.Context(), colDocuments, url.Values{
		"filter":  {fmt.Sprintf("car='%s'", carID)},
		"perPage": {"500"},
	})
	if err != nil {
		return nil, err
	}
	var recs []documentRecord
	if err := json.Unmarshal(res.Items, &recs); err != nil {
		return nil, err
	}
	now := time.Now()
	out := make([]models.CarDocument, 0, len(recs))
	for _, rec := range recs {
		d := rec.toModel()
		d.ComputeExpiry(now)
		out = append(out, d)
	}

	// Sorted here rather than by PocketBase: it orders a blank expiry_date ahead
	// of every real date, which would file the documents that never expire above
	// the ones that have already lapsed — the exact inverse of what this list is
	// for. Everything with a renewal date comes first, soonest at the top.
	sort.SliceStable(out, func(i, j int) bool {
		a, b := out[i], out[j]
		ae, be := a.ExpiryDate != nil, b.ExpiryDate != nil
		if ae != be {
			return ae
		}
		if ae && be && !a.ExpiryDate.Equal(*b.ExpiryDate) {
			return a.ExpiryDate.Before(*b.ExpiryDate)
		}
		return a.Title < b.Title
	})
	return out, nil
}

func (s *Server) getDocument(w http.ResponseWriter, r *http.Request) {
	var rec documentRecord
	if err := s.pb.GetOne(r.Context(), colDocuments, r.PathValue("id"), &rec); err != nil {
		writePBError(w, err)
		return
	}
	if !s.requireCarAccess(w, r, rec.Car, accessRead) {
		return
	}
	d := rec.toModel()
	d.ComputeExpiry(time.Now())
	writeJSON(w, http.StatusOK, d)
}

func (s *Server) createDocument(w http.ResponseWriter, r *http.Request) {
	var in models.CarDocument
	if err := decodeJSON(r, &in); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	if in.Type == "" {
		in.Type = "other"
	}
	if msg := validateDocument(in); msg != "" {
		writeError(w, http.StatusBadRequest, msg)
		return
	}
	if !s.requireCarAccess(w, r, in.Car, accessWrite) {
		return
	}
	var rec documentRecord
	if err := s.pb.Create(r.Context(), colDocuments, documentPayload(in), &rec); err != nil {
		writePBError(w, err)
		return
	}
	d := rec.toModel()
	d.ComputeExpiry(time.Now())
	writeJSON(w, http.StatusCreated, d)
}

func (s *Server) updateDocument(w http.ResponseWriter, r *http.Request) {
	var in models.CarDocument
	if err := decodeJSON(r, &in); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	var existing documentRecord
	if err := s.pb.GetOne(r.Context(), colDocuments, r.PathValue("id"), &existing); err != nil {
		writePBError(w, err)
		return
	}
	if !s.requireCarAccess(w, r, existing.Car, accessWrite) {
		return
	}
	in.Car = existing.Car // the document's car is not reassignable via PATCH
	if in.Type == "" {
		in.Type = "other"
	}
	if msg := validateDocument(in); msg != "" {
		writeError(w, http.StatusBadRequest, msg)
		return
	}
	var rec documentRecord
	if err := s.pb.Update(r.Context(), colDocuments, r.PathValue("id"), documentPayload(in), &rec); err != nil {
		writePBError(w, err)
		return
	}
	d := rec.toModel()
	d.ComputeExpiry(time.Now())
	writeJSON(w, http.StatusOK, d)
}

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

func validateDocument(d models.CarDocument) string {
	switch {
	case d.Car == "":
		return "car is required"
	case strings.TrimSpace(d.Title) == "":
		return "title is required"
	case !documentTypes[d.Type]:
		return "type must be one of: insurance, pollution, registration, inspection, roadTax, warranty, other"
	case d.Cost < 0:
		return "cost cannot be negative"
	}
	if d.IssueDate != nil && d.ExpiryDate != nil && d.ExpiryDate.Before(*d.IssueDate) {
		return "expiry date cannot be before the issue date"
	}
	return ""
}
