package handlers

import (
	"encoding/json"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"
	"time"

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

	"github.com/forgeo/forgebucket/internal/api/middleware"
	"github.com/forgeo/forgebucket/internal/models"
)

// ── shared lookup ─────────────────────────────────────────────────────────────

type WorkflowHandler struct{ db *xorm.Engine }

func NewWorkflowHandler(db *xorm.Engine) *WorkflowHandler { return &WorkflowHandler{db: db} }

func (h *WorkflowHandler) resolveRepo(w http.ResponseWriter, r *http.Request) (*models.Repository, *models.User, bool) {
	repo, ok := resolveRepo(h.db, w, r)
	if !ok {
		return nil, nil, false
	}
	var owner models.User
	h.db.ID(repo.OwnerID).Get(&owner)
	return repo, &owner, true
}

func (h *WorkflowHandler) canManage(repo *models.Repository, callerID int64) bool {
	if callerID == repo.OwnerID {
		return true
	}
	var m models.RepoMember
	found, _ := h.db.Where("repo_id = ? AND user_id = ? AND permission = 'admin'", repo.ID, callerID).Get(&m)
	return found
}

// ── branch protections ────────────────────────────────────────────────────────

type branchProtectionResponse struct {
	ID             int64  `json:"id"`
	Pattern        string `json:"pattern"`
	RequirePR      bool   `json:"requirePR"`
	BlockForcePush bool   `json:"blockForcePush"`
	AllowedUsers   string `json:"allowedUsers"`
	CreatedAt      string `json:"createdAt"`
}

func toBranchProtResp(bp models.BranchProtection) branchProtectionResponse {
	return branchProtectionResponse{
		ID:             bp.ID,
		Pattern:        bp.Pattern,
		RequirePR:      bp.RequirePR,
		BlockForcePush: bp.BlockForcePush,
		AllowedUsers:   bp.AllowedUsers,
		CreatedAt:      bp.CreatedAt.Format(time.RFC3339),
	}
}

func (h *WorkflowHandler) ListBranchProtections(w http.ResponseWriter, r *http.Request) {
	repo, _, ok := h.resolveRepo(w, r)
	if !ok {
		return
	}
	var bps []models.BranchProtection
	h.db.Where("repo_id = ?", repo.ID).OrderBy("created_at").Find(&bps)
	resp := make([]branchProtectionResponse, len(bps))
	for i, bp := range bps {
		resp[i] = toBranchProtResp(bp)
	}
	jsonOK(w, resp)
}

func (h *WorkflowHandler) CreateBranchProtection(w http.ResponseWriter, r *http.Request) {
	repo, _, ok := h.resolveRepo(w, r)
	if !ok {
		return
	}
	callerID, _ := middleware.UserIDFromContext(r.Context())
	if !h.canManage(repo, callerID) {
		jsonError(w, "forbidden", http.StatusForbidden)
		return
	}

	var body struct {
		Pattern        string `json:"pattern"`
		RequirePR      bool   `json:"requirePR"`
		BlockForcePush bool   `json:"blockForcePush"`
		AllowedUsers   string `json:"allowedUsers"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Pattern == "" {
		jsonError(w, "pattern is required", http.StatusBadRequest)
		return
	}

	bp := &models.BranchProtection{
		RepoID:         repo.ID,
		Pattern:        body.Pattern,
		RequirePR:      body.RequirePR,
		BlockForcePush: body.BlockForcePush,
		AllowedUsers:   body.AllowedUsers,
	}
	if _, err := h.db.Insert(bp); err != nil {
		jsonError(w, "could not create protection", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(toBranchProtResp(*bp))
}

func (h *WorkflowHandler) UpdateBranchProtection(w http.ResponseWriter, r *http.Request) {
	repo, _, ok := h.resolveRepo(w, r)
	if !ok {
		return
	}
	callerID, _ := middleware.UserIDFromContext(r.Context())
	if !h.canManage(repo, callerID) {
		jsonError(w, "forbidden", http.StatusForbidden)
		return
	}

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

	var bp models.BranchProtection
	if found, _ := h.db.Where("id = ? AND repo_id = ?", bpID, repo.ID).Get(&bp); !found {
		jsonError(w, "rule not found", http.StatusNotFound)
		return
	}

	var body struct {
		RequirePR      bool   `json:"requirePR"`
		BlockForcePush bool   `json:"blockForcePush"`
		AllowedUsers   string `json:"allowedUsers"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		jsonError(w, "invalid body", http.StatusBadRequest)
		return
	}
	bp.RequirePR = body.RequirePR
	bp.BlockForcePush = body.BlockForcePush
	bp.AllowedUsers = body.AllowedUsers
	h.db.ID(bp.ID).Cols("require_pr", "block_force_push", "allowed_users").Update(&bp)
	jsonOK(w, toBranchProtResp(bp))
}

func (h *WorkflowHandler) DeleteBranchProtection(w http.ResponseWriter, r *http.Request) {
	repo, _, ok := h.resolveRepo(w, r)
	if !ok {
		return
	}
	callerID, _ := middleware.UserIDFromContext(r.Context())
	if !h.canManage(repo, callerID) {
		jsonError(w, "forbidden", http.StatusForbidden)
		return
	}
	bpID, _ := strconv.ParseInt(chi.URLParam(r, "bpID"), 10, 64)
	h.db.Where("id = ? AND repo_id = ?", bpID, repo.ID).Delete(&models.BranchProtection{})
	w.WriteHeader(http.StatusNoContent)
}

// CheckBranchProtection returns a deny reason if the push violates a protection rule,
// or "" if the push is allowed. Called from githttp.go before running the backend.
func CheckBranchProtection(db *xorm.Engine, repoID int64, pusherUsername, refname string, isForcePush bool) string {
	branchName := strings.TrimPrefix(refname, "refs/heads/")
	if branchName == refname {
		return "" // not a branch ref
	}

	var protections []models.BranchProtection
	db.Where("repo_id = ?", repoID).Find(&protections)

	for _, bp := range protections {
		matched, err := filepath.Match(bp.Pattern, branchName)
		if err != nil || !matched {
			continue
		}
		// Check if the pusher is in the allowed list.
		if bp.AllowedUsers != "" {
			allowed := false
			for _, u := range strings.Split(bp.AllowedUsers, ",") {
				if strings.TrimSpace(u) == pusherUsername {
					allowed = true
					break
				}
			}
			if allowed {
				continue
			}
		}
		// Enforce rules.
		if bp.RequirePR {
			return "push rejected: '" + branchName + "' is protected and requires a pull request"
		}
		if bp.BlockForcePush && isForcePush {
			return "push rejected: force push to '" + branchName + "' is not allowed"
		}
	}
	return ""
}

// ── branching model ───────────────────────────────────────────────────────────

type branchingModelResponse struct {
	Enabled        bool   `json:"enabled"`
	FeaturePrefix  string `json:"featurePrefix"`
	BugfixPrefix   string `json:"bugfixPrefix"`
	ReleasePrefix  string `json:"releasePrefix"`
	HotfixPrefix   string `json:"hotfixPrefix"`
}

func (h *WorkflowHandler) GetBranchingModel(w http.ResponseWriter, r *http.Request) {
	repo, _, ok := h.resolveRepo(w, r)
	if !ok {
		return
	}
	var bm models.BranchingModel
	found, _ := h.db.Where("repo_id = ?", repo.ID).Get(&bm)
	if !found {
		// Return sensible defaults.
		jsonOK(w, branchingModelResponse{
			Enabled: false, FeaturePrefix: "feature/", BugfixPrefix: "bugfix/",
			ReleasePrefix: "release/", HotfixPrefix: "hotfix/",
		})
		return
	}
	jsonOK(w, branchingModelResponse{
		Enabled: bm.Enabled, FeaturePrefix: bm.FeaturePrefix, BugfixPrefix: bm.BugfixPrefix,
		ReleasePrefix: bm.ReleasePrefix, HotfixPrefix: bm.HotfixPrefix,
	})
}

func (h *WorkflowHandler) UpdateBranchingModel(w http.ResponseWriter, r *http.Request) {
	repo, _, ok := h.resolveRepo(w, r)
	if !ok {
		return
	}
	callerID, _ := middleware.UserIDFromContext(r.Context())
	if !h.canManage(repo, callerID) {
		jsonError(w, "forbidden", http.StatusForbidden)
		return
	}

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

	var bm models.BranchingModel
	found, _ := h.db.Where("repo_id = ?", repo.ID).Get(&bm)
	bm.RepoID = repo.ID
	bm.Enabled = body.Enabled
	bm.FeaturePrefix = body.FeaturePrefix
	bm.BugfixPrefix = body.BugfixPrefix
	bm.ReleasePrefix = body.ReleasePrefix
	bm.HotfixPrefix = body.HotfixPrefix

	if found {
		h.db.ID(bm.ID).Cols("enabled", "feature_prefix", "bugfix_prefix", "release_prefix", "hotfix_prefix").Update(&bm)
	} else {
		h.db.Insert(&bm)
	}
	jsonOK(w, body)
}

// ── merge strategies ──────────────────────────────────────────────────────────

type mergeStrategiesResponse struct {
	AllowMergeCommit bool `json:"allowMergeCommit"`
	AllowSquash      bool `json:"allowSquash"`
	AllowRebase      bool `json:"allowRebase"`
}

func (h *WorkflowHandler) GetMergeStrategies(w http.ResponseWriter, r *http.Request) {
	repo, _, ok := h.resolveRepo(w, r)
	if !ok {
		return
	}
	var ms models.MergeStrategies
	found, _ := h.db.Where("repo_id = ?", repo.ID).Get(&ms)
	if !found {
		jsonOK(w, mergeStrategiesResponse{AllowMergeCommit: true, AllowSquash: true, AllowRebase: true})
		return
	}
	jsonOK(w, mergeStrategiesResponse{
		AllowMergeCommit: ms.AllowMergeCommit,
		AllowSquash:      ms.AllowSquash,
		AllowRebase:      ms.AllowRebase,
	})
}

func (h *WorkflowHandler) UpdateMergeStrategies(w http.ResponseWriter, r *http.Request) {
	repo, _, ok := h.resolveRepo(w, r)
	if !ok {
		return
	}
	callerID, _ := middleware.UserIDFromContext(r.Context())
	if !h.canManage(repo, callerID) {
		jsonError(w, "forbidden", http.StatusForbidden)
		return
	}

	var body mergeStrategiesResponse
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		jsonError(w, "invalid body", http.StatusBadRequest)
		return
	}
	if !body.AllowMergeCommit && !body.AllowSquash && !body.AllowRebase {
		jsonError(w, "at least one merge strategy must be enabled", http.StatusBadRequest)
		return
	}

	var ms models.MergeStrategies
	found, _ := h.db.Where("repo_id = ?", repo.ID).Get(&ms)
	ms.RepoID = repo.ID
	ms.AllowMergeCommit = body.AllowMergeCommit
	ms.AllowSquash = body.AllowSquash
	ms.AllowRebase = body.AllowRebase

	if found {
		h.db.ID(ms.ID).Cols("allow_merge_commit", "allow_squash", "allow_rebase").Update(&ms)
	} else {
		h.db.Insert(&ms)
	}
	jsonOK(w, body)
}

// GetAllowedStrategies returns the allowed strategy set for a repo (used by PR merge handler).
func GetAllowedStrategies(db *xorm.Engine, repoID int64) map[string]bool {
	var ms models.MergeStrategies
	if found, _ := db.Where("repo_id = ?", repoID).Get(&ms); !found {
		return map[string]bool{"merge": true, "squash": true, "rebase": true}
	}
	return map[string]bool{
		"merge":  ms.AllowMergeCommit,
		"squash": ms.AllowSquash,
		"rebase": ms.AllowRebase,
	}
}
