package api

import (
	"encoding/json"
	"net/http"
	"strconv"
	"strings"
	"woke/db"
)

// Notifier is called after mutating operations to notify the TUI.
type Notifier func()

// Server provides a REST API for alarm CRUD.
type Server struct {
	store    *db.Store
	mux      *http.ServeMux
	notify   Notifier
	password string
}

func New(store *db.Store, password string, notify Notifier) *Server {
	s := &Server{store: store, notify: notify, password: password}
	s.mux = http.NewServeMux()
	s.mux.HandleFunc("GET /api/alarms", s.listAlarms)
	s.mux.HandleFunc("GET /api/alarms/{id}", s.getAlarm)
	s.mux.HandleFunc("POST /api/alarms", s.createAlarm)
	s.mux.HandleFunc("PUT /api/alarms/{id}", s.updateAlarm)
	s.mux.HandleFunc("DELETE /api/alarms/{id}", s.deleteAlarm)
	s.mux.HandleFunc("PATCH /api/alarms/{id}/toggle", s.toggleAlarm)
	return s
}

func (s *Server) Handler() http.Handler {
	var handler http.Handler = s.mux

	// Wrap with auth middleware if password is set
	if s.password != "" {
		authHandler := handler
		handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			auth := r.Header.Get("Authorization")
			if !strings.HasPrefix(auth, "Bearer ") || auth[7:] != s.password {
				writeError(w, http.StatusUnauthorized, "invalid or missing authorization")
				return
			}
			authHandler.ServeHTTP(w, r)
		})
	}

	// Wrap with CORS middleware (allow all origins)
	corsHandler := handler
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

		// Handle preflight requests
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusNoContent)
			return
		}

		corsHandler.ServeHTTP(w, r)
	})
}

type alarmRequest struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Time        string `json:"time"`
	Trigger     string `json:"trigger"`
	SoundPath   string `json:"sound_path"`
	Enabled     *bool  `json:"enabled"`
}

type alarmResponse struct {
	ID            int     `json:"id"`
	Name          string  `json:"name"`
	Description   string  `json:"description"`
	Time          string  `json:"time"`
	Trigger       string  `json:"trigger"`
	SoundPath     string  `json:"sound_path"`
	Enabled       bool    `json:"enabled"`
	CreatedAt     string  `json:"created_at"`
	UpdatedAt     string  `json:"updated_at"`
	LastTriggered *string `json:"last_triggered"`
	SnoozeCount   int     `json:"snooze_count"`
}

func toResponse(a db.Alarm) alarmResponse {
	r := alarmResponse{
		ID:          a.ID,
		Name:        a.Name,
		Description: a.Description,
		Time:        a.Time,
		Trigger:     a.Trigger,
		SoundPath:   a.SoundPath,
		Enabled:     a.Enabled,
		CreatedAt:   a.CreatedAt.Format("2006-01-02T15:04:05Z"),
		UpdatedAt:   a.UpdatedAt.Format("2006-01-02T15:04:05Z"),
		SnoozeCount: a.SnoozeCount,
	}
	if a.LastTriggered != nil {
		t := a.LastTriggered.Format("2006-01-02T15:04:05Z")
		r.LastTriggered = &t
	}
	return r
}

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 (s *Server) listAlarms(w http.ResponseWriter, r *http.Request) {
	alarms, err := s.store.ListAlarms()
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to list alarms")
		return
	}
	resp := make([]alarmResponse, len(alarms))
	for i, a := range alarms {
		resp[i] = toResponse(a)
	}
	writeJSON(w, http.StatusOK, resp)
}

func (s *Server) getAlarm(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(r.PathValue("id"))
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid id")
		return
	}
	alarm, err := s.store.GetAlarm(id)
	if err != nil {
		writeError(w, http.StatusNotFound, "alarm not found")
		return
	}
	writeJSON(w, http.StatusOK, toResponse(alarm))
}

func (s *Server) createAlarm(w http.ResponseWriter, r *http.Request) {
	var req alarmRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid JSON")
		return
	}
	if req.Name == "" {
		writeError(w, http.StatusBadRequest, "name is required")
		return
	}
	if req.Trigger == "" {
		writeError(w, http.StatusBadRequest, "trigger is required")
		return
	}

	alarm := db.Alarm{
		Name:        req.Name,
		Description: req.Description,
		Time:        req.Time,
		Trigger:     req.Trigger,
		SoundPath:   req.SoundPath,
		Enabled:     true,
	}
	if alarm.SoundPath == "" {
		alarm.SoundPath = "default"
	}
	if req.Enabled != nil {
		alarm.Enabled = *req.Enabled
	}

	id, err := s.store.CreateAlarm(alarm)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to create alarm")
		return
	}

	created, err := s.store.GetAlarm(id)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to read created alarm")
		return
	}
	s.notify()
	writeJSON(w, http.StatusCreated, toResponse(created))
}

func (s *Server) updateAlarm(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(r.PathValue("id"))
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid id")
		return
	}

	existing, err := s.store.GetAlarm(id)
	if err != nil {
		writeError(w, http.StatusNotFound, "alarm not found")
		return
	}

	var req alarmRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid JSON")
		return
	}

	if req.Name != "" {
		existing.Name = req.Name
	}
	if req.Description != "" {
		existing.Description = req.Description
	}
	if req.Time != "" {
		existing.Time = req.Time
	}
	if req.Trigger != "" {
		existing.Trigger = req.Trigger
	}
	if req.SoundPath != "" {
		existing.SoundPath = req.SoundPath
	}
	if req.Enabled != nil {
		existing.Enabled = *req.Enabled
	}

	if err := s.store.UpdateAlarm(existing); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to update alarm")
		return
	}

	updated, _ := s.store.GetAlarm(id)
	s.notify()
	writeJSON(w, http.StatusOK, toResponse(updated))
}

func (s *Server) deleteAlarm(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(r.PathValue("id"))
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid id")
		return
	}

	if _, err := s.store.GetAlarm(id); err != nil {
		writeError(w, http.StatusNotFound, "alarm not found")
		return
	}

	if err := s.store.DeleteAlarm(id); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to delete alarm")
		return
	}
	s.notify()
	w.WriteHeader(http.StatusNoContent)
}

func (s *Server) toggleAlarm(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(r.PathValue("id"))
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid id")
		return
	}

	if _, err := s.store.GetAlarm(id); err != nil {
		writeError(w, http.StatusNotFound, "alarm not found")
		return
	}

	if err := s.store.ToggleAlarm(id); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to toggle alarm")
		return
	}

	toggled, _ := s.store.GetAlarm(id)
	s.notify()
	writeJSON(w, http.StatusOK, toResponse(toggled))
}
