package api

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

	"drivervault/apiserver/internal/models"
)

// Maintenance log: workshop visits and repairs.
//
// This is deliberately NOT the service history. A ServiceRecord is the routine,
// interval-driven schedule the spreadsheet was built around (oil at 15000 km,
// filters once a year) and it drives the next-service-due calculation. A
// MaintenanceEntry is unplanned or one-off work done at a garage — a failed
// alternator, a clutch, bodywork after a scrape — which has a workshop, an
// invoice, and a labour bill, and no bearing on the service interval.

// maintenanceTypes are the kinds of visit the log accepts. Anything else is
// rejected rather than silently stored, so the UI's filters stay meaningful.
var maintenanceTypes = map[string]bool{
	"repair":      true,
	"inspection":  true,
	"bodywork":    true,
	"tyres":       true,
	"diagnostics": true,
	"recall":      true,
	"warranty":    true,
	"other":       true,
}

var maintenanceStatuses = map[string]bool{
	"scheduled":   true,
	"in_progress": true,
	"completed":   true,
}

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

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

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

// fetchMaintenance loads a car's maintenance entries newest-first with derived
// cost and warranty fields filled in.
func (s *Server) fetchMaintenance(r *http.Request, carID string) ([]models.MaintenanceEntry, error) {
	res, err := s.pb.List(r.Context(), colMaintenance, url.Values{
		"filter":  {fmt.Sprintf("car='%s'", carID)},
		"sort":    {"-date"},
		"perPage": {"500"},
	})
	if err != nil {
		return nil, err
	}
	var recs []maintenanceRecord
	if err := json.Unmarshal(res.Items, &recs); err != nil {
		return nil, err
	}
	now := time.Now()
	out := make([]models.MaintenanceEntry, 0, len(recs))
	for _, rec := range recs {
		m := rec.toModel()
		m.ComputeMaintenanceDerived(now)
		out = append(out, m)
	}
	return out, nil
}

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

func (s *Server) createMaintenance(w http.ResponseWriter, r *http.Request) {
	var in models.MaintenanceEntry
	if err := decodeJSON(r, &in); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	applyMaintenanceDefaults(&in)
	if msg := validateMaintenance(in); msg != "" {
		writeError(w, http.StatusBadRequest, msg)
		return
	}
	if !s.requireCarAccess(w, r, in.Car, accessWrite) {
		return
	}
	var rec maintenanceRecord
	if err := s.pb.Create(r.Context(), colMaintenance, maintenancePayload(in), &rec); err != nil {
		writePBError(w, err)
		return
	}
	// Only completed work proves the car actually reached that odometer; a
	// scheduled visit carries an estimate of where it will be.
	if in.Status == "completed" {
		s.advanceOdometer(r, in.Car, in.Km)
	}
	m := rec.toModel()
	m.ComputeMaintenanceDerived(time.Now())
	writeJSON(w, http.StatusCreated, m)
}

func (s *Server) updateMaintenance(w http.ResponseWriter, r *http.Request) {
	var in models.MaintenanceEntry
	if err := decodeJSON(r, &in); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}
	var existing maintenanceRecord
	if err := s.pb.GetOne(r.Context(), colMaintenance, r.PathValue("id"), &existing); err != nil {
		writePBError(w, err)
		return
	}
	if !s.requireCarAccess(w, r, existing.Car, accessWrite) {
		return
	}
	in.Car = existing.Car // the record's car is not reassignable via PATCH
	applyMaintenanceDefaults(&in)
	if msg := validateMaintenance(in); msg != "" {
		writeError(w, http.StatusBadRequest, msg)
		return
	}
	var rec maintenanceRecord
	if err := s.pb.Update(r.Context(), colMaintenance, r.PathValue("id"), maintenancePayload(in), &rec); err != nil {
		writePBError(w, err)
		return
	}
	if in.Status == "completed" {
		s.advanceOdometer(r, rec.Car, in.Km)
	}
	m := rec.toModel()
	m.ComputeMaintenanceDerived(time.Now())
	writeJSON(w, http.StatusOK, m)
}

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

func applyMaintenanceDefaults(m *models.MaintenanceEntry) {
	if m.Type == "" {
		m.Type = "repair"
	}
	if m.Status == "" {
		m.Status = "completed"
	}
}

func validateMaintenance(m models.MaintenanceEntry) string {
	switch {
	case m.Car == "":
		return "car is required"
	case m.Date.IsZero():
		return "date is required"
	case m.Description == "":
		return "description is required"
	case !maintenanceTypes[m.Type]:
		return "type must be one of: repair, inspection, bodywork, tyres, diagnostics, recall, warranty, other"
	case !maintenanceStatuses[m.Status]:
		return "status must be one of: scheduled, in_progress, completed"
	case m.LaborCost < 0 || m.PartsCost < 0:
		return "costs cannot be negative"
	}
	return ""
}
