package handlers

import (
	"encoding/json"
	"net/http"
	"strconv"
	"time"

	"github.com/go-chi/chi/v5"
	"xorm.io/xorm"

	"github.com/forgeo/forgebucket/internal/events"
	"github.com/forgeo/forgebucket/internal/models"
)

type GitOpsHandler struct {
	db  *xorm.Engine
	bus events.EventBus
}

func NewGitOpsHandler(db *xorm.Engine, bus events.EventBus) *GitOpsHandler {
	return &GitOpsHandler{db: db, bus: bus}
}

// GetConfig returns the GitOpsConfig for an environment, or 404 if not configured.
func (h *GitOpsHandler) GetConfig(w http.ResponseWriter, r *http.Request) {
	env, ok := h.resolveGitOpsEnv(w, r)
	if !ok {
		return
	}
	var cfg models.GitOpsConfig
	if found, _ := h.db.Where("env_id = ?", env.ID).Get(&cfg); !found {
		jsonError(w, "gitops not configured for this environment", http.StatusNotFound)
		return
	}
	jsonOK(w, cfg)
}

// UpsertConfig creates or replaces the GitOpsConfig for an environment.
func (h *GitOpsHandler) UpsertConfig(w http.ResponseWriter, r *http.Request) {
	env, ok := h.resolveGitOpsEnv(w, r)
	if !ok {
		return
	}

	var body struct {
		Branch       string `json:"branch"`
		AutoSync     bool   `json:"autoSync"`
		SyncInterval int    `json:"syncInterval"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		jsonError(w, "invalid request body", http.StatusBadRequest)
		return
	}
	if body.Branch == "" {
		jsonError(w, "branch is required", http.StatusBadRequest)
		return
	}

	var cfg models.GitOpsConfig
	exists, _ := h.db.Where("env_id = ?", env.ID).Get(&cfg)

	cfg.EnvID = env.ID
	cfg.RepoID = env.RepoID
	cfg.Branch = body.Branch
	cfg.AutoSync = body.AutoSync
	cfg.SyncInterval = body.SyncInterval
	if cfg.SyncStatus == "" {
		cfg.SyncStatus = "unknown"
	}

	var err error
	if exists {
		_, err = h.db.ID(cfg.ID).Cols("branch", "auto_sync", "sync_interval").Update(&cfg)
	} else {
		_, err = h.db.Insert(&cfg)
	}
	if err != nil {
		jsonError(w, "could not save gitops config", http.StatusInternalServerError)
		return
	}
	jsonOK(w, cfg)
}

// DeleteConfig removes the GitOpsConfig for an environment without deleting deployments.
func (h *GitOpsHandler) DeleteConfig(w http.ResponseWriter, r *http.Request) {
	env, ok := h.resolveGitOpsEnv(w, r)
	if !ok {
		return
	}
	if _, err := h.db.Where("env_id = ?", env.ID).Delete(&models.GitOpsConfig{}); err != nil {
		jsonError(w, "could not delete gitops config", http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// TriggerSync manually initiates a reconciliation for the environment.
func (h *GitOpsHandler) TriggerSync(w http.ResponseWriter, r *http.Request) {
	env, ok := h.resolveGitOpsEnv(w, r)
	if !ok {
		return
	}

	var cfg models.GitOpsConfig
	if found, _ := h.db.Where("env_id = ?", env.ID).Get(&cfg); !found {
		jsonError(w, "gitops not configured for this environment", http.StatusNotFound)
		return
	}
	if cfg.DesiredSHA == "" {
		jsonError(w, "no desired SHA known yet — push to the configured branch first", http.StatusConflict)
		return
	}
	if cfg.SyncStatus == "syncing" {
		jsonError(w, "a sync is already in progress", http.StatusConflict)
		return
	}

	now := time.Now().UTC()
	deploy := &models.Deployment{
		EnvID:       env.ID,
		RepoID:      env.RepoID,
		SHA:         cfg.DesiredSHA,
		Ref:         "refs/heads/" + cfg.Branch,
		Status:      models.DeployStatusPending,
		TriggeredBy: "gitops-manual",
		Description: "Manual GitOps sync",
		StartedAt:   &now,
	}
	if _, err := h.db.Insert(deploy); err != nil {
		jsonError(w, "could not create deployment", http.StatusInternalServerError)
		return
	}

	cfg.SyncStatus = "syncing"
	h.db.ID(cfg.ID).Cols("sync_status").Update(&cfg) //nolint:errcheck

	h.bus.Publish(events.SubjectDeploymentStarted, events.DeploymentEvent{ //nolint:errcheck
		DeploymentID: deploy.ID,
		EnvID:        env.ID,
		EnvName:      env.Name,
		RepoID:       deploy.RepoID,
		SHA:          deploy.SHA,
		Ref:          deploy.Ref,
		Status:       string(deploy.Status),
		TriggeredBy:  deploy.TriggeredBy,
	})

	w.WriteHeader(http.StatusCreated)
	jsonOK(w, deploy)
}

// GetDriftStatus returns the current sync status and SHA comparison for an environment.
func (h *GitOpsHandler) GetDriftStatus(w http.ResponseWriter, r *http.Request) {
	env, ok := h.resolveGitOpsEnv(w, r)
	if !ok {
		return
	}

	var cfg models.GitOpsConfig
	if found, _ := h.db.Where("env_id = ?", env.ID).Get(&cfg); !found {
		jsonError(w, "gitops not configured for this environment", http.StatusNotFound)
		return
	}

	type driftStatus struct {
		SyncStatus string `json:"syncStatus"`
		DesiredSHA string `json:"desiredSha"`
		ActualSHA  string `json:"actualSha"`
		Branch     string `json:"branch"`
		IsDrifted  bool   `json:"isDrifted"`
	}
	jsonOK(w, driftStatus{
		SyncStatus: cfg.SyncStatus,
		DesiredSHA: cfg.DesiredSHA,
		ActualSHA:  cfg.ActualSHA,
		Branch:     cfg.Branch,
		IsDrifted:  cfg.DesiredSHA != cfg.ActualSHA && cfg.DesiredSHA != "",
	})
}

// ListDriftEvents returns the drift history for an environment, newest first.
func (h *GitOpsHandler) ListDriftEvents(w http.ResponseWriter, r *http.Request) {
	env, ok := h.resolveGitOpsEnv(w, r)
	if !ok {
		return
	}

	limit := 50
	if l, err := strconv.Atoi(r.URL.Query().Get("limit")); err == nil && l > 0 && l <= 200 {
		limit = l
	}

	var drifts []models.GitOpsDriftEvent
	if err := h.db.Where("env_id = ?", env.ID).Desc("id").Limit(limit).Find(&drifts); err != nil {
		jsonError(w, "could not list drift events", http.StatusInternalServerError)
		return
	}
	if drifts == nil {
		drifts = []models.GitOpsDriftEvent{}
	}
	jsonOK(w, drifts)
}

// AcknowledgeDrift marks a drift event as acknowledged without triggering a sync.
func (h *GitOpsHandler) AcknowledgeDrift(w http.ResponseWriter, r *http.Request) {
	env, ok := h.resolveGitOpsEnv(w, r)
	if !ok {
		return
	}

	driftID, err := strconv.ParseInt(chi.URLParam(r, "driftID"), 10, 64)
	if err != nil {
		jsonError(w, "invalid drift event ID", http.StatusBadRequest)
		return
	}

	var drift models.GitOpsDriftEvent
	if found, _ := h.db.Where("id = ? AND env_id = ?", driftID, env.ID).Get(&drift); !found {
		jsonError(w, "drift event not found", http.StatusNotFound)
		return
	}
	if drift.ResolvedAt != nil {
		jsonError(w, "drift event is already resolved", http.StatusConflict)
		return
	}

	now := time.Now().UTC()
	drift.SyncStatus = "acknowledged"
	drift.ResolvedAt = &now
	if _, err := h.db.ID(drift.ID).Cols("sync_status", "resolved_at").Update(&drift); err != nil {
		jsonError(w, "could not acknowledge drift", http.StatusInternalServerError)
		return
	}
	jsonOK(w, drift)
}

// ── Helpers ───────────────────────────────────────────────────────────────────

func (h *GitOpsHandler) resolveGitOpsEnv(w http.ResponseWriter, r *http.Request) (*models.Environment, bool) {
	repoID, ok := resolveRepoID(h.db, w, r)
	if !ok {
		return nil, false
	}
	envName := chi.URLParam(r, "envName")
	var env models.Environment
	if found, _ := h.db.Where("repo_id = ? AND name = ?", repoID, envName).Get(&env); !found {
		jsonError(w, "environment not found", http.StatusNotFound)
		return nil, false
	}
	return &env, true
}
