package handlers

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

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

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

type PRHandler struct {
	db *xorm.Engine
}

func NewPRHandler(db *xorm.Engine) *PRHandler {
	return &PRHandler{db: db}
}

func (h *PRHandler) List(w http.ResponseWriter, r *http.Request) {
	repoID, ok := h.repoIDFromURL(w, r)
	if !ok {
		return
	}

	status := r.URL.Query().Get("status")

	sess := h.db.Where("repo_id = ?", repoID)
	if status != "" {
		sess = sess.And("status = ?", status)
	}

	var prs []models.PullRequest
	if err := sess.Find(&prs); err != nil {
		jsonError(w, "could not list pull requests", http.StatusInternalServerError)
		return
	}
	if prs == nil {
		prs = []models.PullRequest{}
	}
	jsonOK(w, prs)
}

func (h *PRHandler) Create(w http.ResponseWriter, r *http.Request) {
	repoID, ok := h.repoIDFromURL(w, r)
	if !ok {
		return
	}
	authorID, _ := middleware.UserIDFromContext(r.Context())

	var body struct {
		Title        string `json:"title"`
		Body         string `json:"body"`
		SourceBranch string `json:"sourceBranch"`
		TargetBranch string `json:"targetBranch"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		jsonError(w, "invalid request body", http.StatusBadRequest)
		return
	}
	if body.Title == "" || body.SourceBranch == "" {
		jsonError(w, "title and sourceBranch are required", http.StatusBadRequest)
		return
	}
	if body.TargetBranch == "" {
		body.TargetBranch = "main"
	}

	pr := &models.PullRequest{
		RepoID:       repoID,
		AuthorID:     authorID,
		Title:        body.Title,
		Body:         body.Body,
		SourceBranch: body.SourceBranch,
		TargetBranch: body.TargetBranch,
		Status:       models.PRStatusOpen,
	}
	if _, err := h.db.Insert(pr); err != nil {
		jsonError(w, "could not create pull request", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(pr)
}

func (h *PRHandler) Get(w http.ResponseWriter, r *http.Request) {
	pr, ok := h.lookupPR(w, r)
	if !ok {
		return
	}
	jsonOK(w, pr)
}

func (h *PRHandler) Merge(w http.ResponseWriter, r *http.Request) {
	pr, ok := h.lookupPR(w, r)
	if !ok {
		return
	}
	if pr.Status != models.PRStatusOpen {
		jsonError(w, "pull request is not open", http.StatusConflict)
		return
	}

	pr.Status = models.PRStatusMerged
	if _, err := h.db.ID(pr.ID).Cols("status").Update(pr); err != nil {
		jsonError(w, "could not merge pull request", http.StatusInternalServerError)
		return
	}
	jsonOK(w, pr)
}

func (h *PRHandler) Close(w http.ResponseWriter, r *http.Request) {
	pr, ok := h.lookupPR(w, r)
	if !ok {
		return
	}
	if pr.Status != models.PRStatusOpen {
		jsonError(w, "pull request is not open", http.StatusConflict)
		return
	}

	pr.Status = models.PRStatusClosed
	if _, err := h.db.ID(pr.ID).Cols("status").Update(pr); err != nil {
		jsonError(w, "could not close pull request", http.StatusInternalServerError)
		return
	}
	jsonOK(w, pr)
}

func (h *PRHandler) repoIDFromURL(w http.ResponseWriter, r *http.Request) (int64, bool) {
	ownerName := chi.URLParam(r, "owner")
	repoName := chi.URLParam(r, "repo")

	var owner models.User
	found, err := h.db.Where("username = ?", ownerName).Get(&owner)
	if err != nil || !found {
		jsonError(w, "repository not found", http.StatusNotFound)
		return 0, false
	}

	var repo models.Repository
	found, err = h.db.Where("owner_id = ? AND name = ?", owner.ID, repoName).Get(&repo)
	if err != nil || !found {
		jsonError(w, "repository not found", http.StatusNotFound)
		return 0, false
	}
	return repo.ID, true
}

func (h *PRHandler) lookupPR(w http.ResponseWriter, r *http.Request) (*models.PullRequest, bool) {
	repoID, ok := h.repoIDFromURL(w, r)
	if !ok {
		return nil, false
	}

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

	var pr models.PullRequest
	found, err := h.db.Where("id = ? AND repo_id = ?", prID, repoID).Get(&pr)
	if err != nil || !found {
		jsonError(w, "pull request not found", http.StatusNotFound)
		return nil, false
	}
	return &pr, true
}
