package api

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"regexp"
	"strconv"

	"github.com/go-chi/chi/v5"
	"github.com/syncserver/internal/models"
)

type SyncPairHandler struct {
	db *sql.DB
}

func NewSyncPairHandler(db *sql.DB) *SyncPairHandler {
	return &SyncPairHandler{db: db}
}

var directionRegex = regexp.MustCompile(`^(push|pull|mirror)$`)

func (h *SyncPairHandler) List(w http.ResponseWriter, r *http.Request) {
	repo := models.NewSyncPairRepository(h.db)
	pairs, err := repo.GetAll()
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to fetch sync pairs")
		return
	}
	out := make([]SyncPairResponse, len(pairs))
	for i, p := range pairs {
		out[i] = syncPairToResp(p)
	}
	writeJSON(w, out)
}

func (h *SyncPairHandler) Get(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid id")
		return
	}
	repo := models.NewSyncPairRepository(h.db)
	p, err := repo.GetByID(id)
	if err == sql.ErrNoRows {
		writeError(w, http.StatusNotFound, "sync pair not found")
		return
	}
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to fetch sync pair")
		return
	}
	writeJSON(w, syncPairToResp(*p))
}

func (h *SyncPairHandler) Create(w http.ResponseWriter, r *http.Request) {
	var req SyncPairRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	if req.Name == "" || req.SourcePath == "" || req.DestPath == "" {
		writeError(w, http.StatusBadRequest, "name, source_path and dest_path are required")
		return
	}
	if req.Direction == "" {
		req.Direction = "push"
	}
	if !directionRegex.MatchString(req.Direction) {
		writeError(w, http.StatusBadRequest, "direction must be push, pull, or mirror")
		return
	}
	if req.RsyncFlags == "" {
		req.RsyncFlags = "-aP"
	}
	if !req.Enabled {
		req.Enabled = true
	}

	sp := &models.SyncPair{
		Name:            req.Name,
		SourceMachineID: req.SourceMachineID,
		SourcePath:      req.SourcePath,
		DestMachineID:   req.DestMachineID,
		DestPath:        req.DestPath,
		Direction:       req.Direction,
		RsyncFlags:      req.RsyncFlags,
		ExcludePatterns: req.ExcludePatterns,
		Enabled:         req.Enabled,
	}

	repo := models.NewSyncPairRepository(h.db)
	id, err := repo.Create(sp)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to create sync pair")
		return
	}
	sp.ID = id
	w.Header().Set("Location", "/api/sync-pairs/"+strconv.FormatInt(id, 10))
	writeJSON(w, syncPairToResp(*sp), http.StatusCreated)
}

func (h *SyncPairHandler) Update(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid id")
		return
	}

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

	if req.Name == "" || req.SourcePath == "" || req.DestPath == "" {
		writeError(w, http.StatusBadRequest, "name, source_path and dest_path are required")
		return
	}
	if !directionRegex.MatchString(req.Direction) {
		writeError(w, http.StatusBadRequest, "direction must be push, pull, or mirror")
		return
	}

	repo := models.NewSyncPairRepository(h.db)
	existing, err := repo.GetByID(id)
	if err == sql.ErrNoRows {
		writeError(w, http.StatusNotFound, "sync pair not found")
		return
	}
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to fetch sync pair")
		return
	}

	existing.Name = req.Name
	existing.SourceMachineID = req.SourceMachineID
	existing.SourcePath = req.SourcePath
	existing.DestMachineID = req.DestMachineID
	existing.DestPath = req.DestPath
	existing.Direction = req.Direction
	existing.RsyncFlags = req.RsyncFlags
	existing.ExcludePatterns = req.ExcludePatterns
	existing.Enabled = req.Enabled

	if err := repo.Update(existing); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to update sync pair")
		return
	}
	writeJSON(w, syncPairToResp(*existing))
}

func (h *SyncPairHandler) Delete(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid id")
		return
	}

	repo := models.NewSyncPairRepository(h.db)
	if err := repo.Delete(id); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to delete sync pair")
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func syncPairToResp(p models.SyncPair) SyncPairResponse {
	return SyncPairResponse{
		ID:              p.ID,
		Name:            p.Name,
		SourceMachineID: p.SourceMachineID,
		SourcePath:      p.SourcePath,
		DestMachineID:   p.DestMachineID,
		DestPath:        p.DestPath,
		Direction:       p.Direction,
		RsyncFlags:      p.RsyncFlags,
		ExcludePatterns: p.ExcludePatterns,
		Enabled:         p.Enabled,
	}
}
