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 IssueHandler struct {
	db *xorm.Engine
}

func NewIssueHandler(db *xorm.Engine) *IssueHandler {
	return &IssueHandler{db: db}
}

type issueResponse struct {
	models.Issue
	AuthorName string `json:"authorName"`
}

func (h *IssueHandler) enrichIssue(issue *models.Issue) issueResponse {
	var author models.User
	h.db.ID(issue.AuthorID).Get(&author)
	return issueResponse{Issue: *issue, AuthorName: author.Username}
}

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

	state := r.URL.Query().Get("state")
	if state == "" {
		state = "open"
	}

	var issues []models.Issue
	if err := h.db.Where("repo_id = ? AND state = ?", repoID, state).
		OrderBy("id DESC").Find(&issues); err != nil {
		jsonError(w, "could not list issues", http.StatusInternalServerError)
		return
	}

	result := make([]issueResponse, len(issues))
	for i := range issues {
		result[i] = h.enrichIssue(&issues[i])
	}
	if result == nil {
		result = []issueResponse{}
	}
	jsonOK(w, result)
}

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

	var body struct {
		Title string `json:"title"`
		Body  string `json:"body"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Title == "" {
		jsonError(w, "title is required", http.StatusBadRequest)
		return
	}

	// Auto-increment issue number per repo
	count, _ := h.db.Where("repo_id = ?", repoID).Count(&models.Issue{})
	number := int(count) + 1

	issue := &models.Issue{
		RepoID:   repoID,
		AuthorID: authorID,
		Number:   number,
		Title:    body.Title,
		Body:     body.Body,
		State:    models.IssueStateOpen,
	}
	if _, err := h.db.Insert(issue); err != nil {
		jsonError(w, "could not create issue", http.StatusInternalServerError)
		return
	}

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

func (h *IssueHandler) Get(w http.ResponseWriter, r *http.Request) {
	issue, ok := h.lookupIssue(w, r)
	if !ok {
		return
	}
	jsonOK(w, h.enrichIssue(issue))
}

func (h *IssueHandler) Close(w http.ResponseWriter, r *http.Request) {
	issue, ok := h.lookupIssue(w, r)
	if !ok {
		return
	}
	issue.State = models.IssueStateClosed
	if _, err := h.db.ID(issue.ID).Cols("state").Update(issue); err != nil {
		jsonError(w, "could not close issue", http.StatusInternalServerError)
		return
	}
	jsonOK(w, h.enrichIssue(issue))
}

func (h *IssueHandler) Reopen(w http.ResponseWriter, r *http.Request) {
	issue, ok := h.lookupIssue(w, r)
	if !ok {
		return
	}
	issue.State = models.IssueStateOpen
	if _, err := h.db.ID(issue.ID).Cols("state").Update(issue); err != nil {
		jsonError(w, "could not reopen issue", http.StatusInternalServerError)
		return
	}
	jsonOK(w, h.enrichIssue(issue))
}

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

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

func (h *IssueHandler) lookupIssue(w http.ResponseWriter, r *http.Request) (*models.Issue, bool) {
	repoID, ok := h.repoID(w, r)
	if !ok {
		return nil, false
	}
	issueNum, err := strconv.Atoi(chi.URLParam(r, "issueNum"))
	if err != nil {
		jsonError(w, "invalid issue number", http.StatusBadRequest)
		return nil, false
	}
	var issue models.Issue
	if found, _ := h.db.Where("repo_id = ? AND number = ?", repoID, issueNum).Get(&issue); !found {
		jsonError(w, "issue not found", http.StatusNotFound)
		return nil, false
	}
	return &issue, true
}
