package handlers

import (
	"database/sql"
	"net/http"

	"github.com/gin-gonic/gin"

	"chat-switchboard/models"
	"chat-switchboard/store"
)

// ── Request / Response Types ────────────────

type createProjectRequest struct {
	Name        string  `json:"name" binding:"required,max=200"`
	Description string  `json:"description,omitempty"`
	Color       *string `json:"color,omitempty"`
	Icon        *string `json:"icon,omitempty"`
}

type updateProjectRequest = models.ProjectPatch

type addChannelRequest struct {
	ChannelID string `json:"channel_id" binding:"required"`
	Position  int    `json:"position"`
}

type reorderChannelsRequest struct {
	ChannelIDs []string `json:"channel_ids" binding:"required"`
}

type addKBRequest struct {
	KBID       string `json:"kb_id" binding:"required"`
	AutoSearch bool   `json:"auto_search"`
}

type addNoteRequest struct {
	NoteID string `json:"note_id" binding:"required"`
}

// ProjectHandler handles project CRUD and associations.
type ProjectHandler struct {
	stores store.Stores
}

// NewProjectHandler creates a new project handler.
func NewProjectHandler(s store.Stores) *ProjectHandler {
	return &ProjectHandler{stores: s}
}

// ── CRUD ────────────────────────────────────

func (h *ProjectHandler) List(c *gin.Context) {
	userID := getUserID(c)
	teamIDs, _ := h.stores.Teams.GetUserTeamIDs(c.Request.Context(), userID)
	includeArchived := c.Query("include_archived") == "true"

	projects, err := h.stores.Projects.ListForUser(c.Request.Context(), userID, teamIDs, includeArchived)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list projects"})
		return
	}
	if projects == nil {
		projects = []models.Project{}
	}
	c.JSON(http.StatusOK, gin.H{"data": projects})
}

func (h *ProjectHandler) Create(c *gin.Context) {
	userID := getUserID(c)
	var req createProjectRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	p := &models.Project{
		Name:        req.Name,
		Description: req.Description,
		Color:       req.Color,
		Icon:        req.Icon,
		Scope:       models.ScopePersonal,
		OwnerID:     userID,
	}

	if err := h.stores.Projects.Create(c.Request.Context(), p); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create project"})
		return
	}

	c.JSON(http.StatusCreated, p)
}

func (h *ProjectHandler) Get(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}
	c.JSON(http.StatusOK, project)
}

func (h *ProjectHandler) Update(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	var req updateProjectRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if err := h.stores.Projects.Update(c.Request.Context(), project.ID, req); err != nil {
		if err == sql.ErrNoRows {
			c.JSON(http.StatusNotFound, gin.H{"error": "project not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update project"})
		return
	}

	// Return refreshed project
	updated, err := h.stores.Projects.GetByID(c.Request.Context(), project.ID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reload project"})
		return
	}
	c.JSON(http.StatusOK, updated)
}

func (h *ProjectHandler) Delete(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	// Only owner or admin can delete
	userID := getUserID(c)
	if project.OwnerID != userID {
		role, _ := c.Get("role")
		if role != "admin" {
			c.JSON(http.StatusForbidden, gin.H{"error": "only the project owner can delete it"})
			return
		}
	}

	if err := h.stores.Projects.Delete(c.Request.Context(), project.ID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete project"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "project deleted"})
}

// ── Channel Association ─────────────────────

func (h *ProjectHandler) AddChannel(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	var req addChannelRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Verify the user owns the channel
	userID := getUserID(c)
	owns, err := h.stores.Channels.UserOwns(c.Request.Context(), req.ChannelID, userID)
	if err != nil || !owns {
		c.JSON(http.StatusForbidden, gin.H{"error": "channel not found or not owned"})
		return
	}

	if err := h.stores.Projects.AddChannel(c.Request.Context(), project.ID, req.ChannelID, req.Position); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to add channel"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "channel added"})
}

func (h *ProjectHandler) RemoveChannel(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	channelID := c.Param("channelId")
	if channelID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "channel_id required"})
		return
	}

	if err := h.stores.Projects.RemoveChannel(c.Request.Context(), project.ID, channelID); err != nil {
		if err == sql.ErrNoRows {
			c.JSON(http.StatusNotFound, gin.H{"error": "channel not in project"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to remove channel"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "channel removed"})
}

func (h *ProjectHandler) ListChannels(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	channels, err := h.stores.Projects.ListChannels(c.Request.Context(), project.ID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list channels"})
		return
	}
	if channels == nil {
		channels = []models.ProjectChannel{}
	}
	c.JSON(http.StatusOK, gin.H{"data": channels})
}

func (h *ProjectHandler) ReorderChannels(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	var req reorderChannelsRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if err := h.stores.Projects.ReorderChannels(c.Request.Context(), project.ID, req.ChannelIDs); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reorder channels"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "channels reordered"})
}

// ── KB Association ──────────────────────────

func (h *ProjectHandler) AddKB(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	var req addKBRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if err := h.stores.Projects.AddKB(c.Request.Context(), project.ID, req.KBID, req.AutoSearch); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to add KB"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "KB added"})
}

func (h *ProjectHandler) RemoveKB(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	kbID := c.Param("kbId")
	if kbID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "kb_id required"})
		return
	}

	if err := h.stores.Projects.RemoveKB(c.Request.Context(), project.ID, kbID); err != nil {
		if err == sql.ErrNoRows {
			c.JSON(http.StatusNotFound, gin.H{"error": "KB not in project"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to remove KB"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "KB removed"})
}

func (h *ProjectHandler) ListKBs(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	kbs, err := h.stores.Projects.ListKBs(c.Request.Context(), project.ID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list KBs"})
		return
	}
	if kbs == nil {
		kbs = []models.ProjectKB{}
	}
	c.JSON(http.StatusOK, gin.H{"data": kbs})
}

// ── Note Association ────────────────────────

func (h *ProjectHandler) AddNote(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	var req addNoteRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if err := h.stores.Projects.AddNote(c.Request.Context(), project.ID, req.NoteID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to add note"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "note added"})
}

func (h *ProjectHandler) RemoveNote(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	noteID := c.Param("noteId")
	if noteID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "note_id required"})
		return
	}

	if err := h.stores.Projects.RemoveNote(c.Request.Context(), project.ID, noteID); err != nil {
		if err == sql.ErrNoRows {
			c.JSON(http.StatusNotFound, gin.H{"error": "note not in project"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to remove note"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "note removed"})
}

func (h *ProjectHandler) ListNotes(c *gin.Context) {
	project, ok := h.loadAndAuthorize(c)
	if !ok {
		return
	}

	notes, err := h.stores.Projects.ListNotes(c.Request.Context(), project.ID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list notes"})
		return
	}
	if notes == nil {
		notes = []models.ProjectNote{}
	}
	c.JSON(http.StatusOK, gin.H{"data": notes})
}

// ── Auth Helper ─────────────────────────────

// ── Admin ────────────────────────────────────

// AdminList returns all projects (no scope filtering). Admin only.
func (h *ProjectHandler) AdminList(c *gin.Context) {
	ctx := c.Request.Context()
	includeArchived := c.Query("include_archived") == "true"

	projects, err := h.stores.Projects.AdminList(ctx, includeArchived)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list projects"})
		return
	}

	type adminProject struct {
		ID           string  `json:"id"`
		Name         string  `json:"name"`
		Description  string  `json:"description"`
		Scope        string  `json:"scope"`
		OwnerID      string  `json:"owner_id"`
		TeamID       *string `json:"team_id,omitempty"`
		IsArchived   bool    `json:"is_archived"`
		CreatedAt    string  `json:"created_at"`
		UpdatedAt    string  `json:"updated_at"`
		ChannelCount int     `json:"channel_count"`
		KBCount      int     `json:"kb_count"`
		NoteCount    int     `json:"note_count"`
		OwnerName    string  `json:"owner_name"`
	}

	result := make([]adminProject, 0, len(projects))
	for _, p := range projects {
		result = append(result, adminProject{
			ID:           p.ID,
			Name:         p.Name,
			Description:  p.Description,
			Scope:        p.Scope,
			OwnerID:      p.OwnerID,
			TeamID:       p.TeamID,
			IsArchived:   p.IsArchived,
			CreatedAt:    p.CreatedAt.Format("2006-01-02T15:04:05Z"),
			UpdatedAt:    p.UpdatedAt.Format("2006-01-02T15:04:05Z"),
			ChannelCount: p.ChannelCount,
			KBCount:      p.KBCount,
			NoteCount:    p.NoteCount,
			OwnerName:    p.OwnerName,
		})
	}
	c.JSON(http.StatusOK, gin.H{"data": result})
}

// ── Auth Helper ─────────────────────────────

// loadAndAuthorize loads the project by :id param and checks user access.
// Returns (project, true) on success, or writes an error response and returns (nil, false).
func (h *ProjectHandler) loadAndAuthorize(c *gin.Context) (*models.Project, bool) {
	projectID := c.Param("id")
	if projectID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "project id required"})
		return nil, false
	}

	userID := getUserID(c)

	// Admins can access all projects
	if c.GetString("role") != "admin" {
		teamIDs, _ := h.stores.Teams.GetUserTeamIDs(c.Request.Context(), userID)

		ok, err := h.stores.Projects.UserCanAccess(c.Request.Context(), userID, projectID, teamIDs)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "access check failed"})
			return nil, false
		}
		if !ok {
			c.JSON(http.StatusNotFound, gin.H{"error": "project not found"})
			return nil, false
		}
	}

	project, err := h.stores.Projects.GetByID(c.Request.Context(), projectID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load project"})
		return nil, false
	}

	return project, true
}
