package handlers

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"

	"switchboard-core/crypto"
	"switchboard-core/database"
	"switchboard-core/models"
	"switchboard-core/store"
)

// ── Request types ───────────────────────────

type createTeamRequest struct {
	Name        string `json:"name" binding:"required,min=1,max=200"`
	Description string `json:"description,omitempty"`
}

type updateTeamRequest struct {
	Name        *string `json:"name,omitempty"`
	Description *string `json:"description,omitempty"`
	IsActive    *bool   `json:"is_active,omitempty"`
	Settings    *string `json:"settings,omitempty"` // JSON string
}

type addMemberRequest struct {
	UserID string `json:"user_id" binding:"required"`
	Role   string `json:"role" binding:"required,min=1,max=50"`
}

type updateMemberRequest struct {
	Role string `json:"role" binding:"required,min=1,max=50"`
}

// ── Handler ─────────────────────────────────

type TeamHandler struct{
	stores store.Stores
	vault  *crypto.KeyResolver
}

func NewTeamHandler(s store.Stores, vault *crypto.KeyResolver) *TeamHandler {
	return &TeamHandler{stores: s, vault: vault}
}

// ── Admin: List All Teams ───────────────────

func (h *TeamHandler) ListTeams(c *gin.Context) {
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "50"))
	if page < 1 {
		page = 1
	}
	if perPage < 1 || perPage > 100 {
		perPage = 50
	}

	all, err := h.stores.Teams.List(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
		return
	}
	if all == nil {
		all = []models.Team{}
	}
	total := len(all)

	// In-memory pagination (teams are low-cardinality)
	offset := (page - 1) * perPage
	end := offset + perPage
	if offset > total {
		offset = total
	}
	if end > total {
		end = total
	}

	c.JSON(http.StatusOK, gin.H{
		"data":     all[offset:end],
		"total":    total,
		"page":     page,
		"per_page": perPage,
	})
}

// ── Admin: Create Team ──────────────────────

func (h *TeamHandler) CreateTeam(c *gin.Context) {
	adminID := getUserID(c)

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

	team := &models.Team{
		Name:        req.Name,
		Description: req.Description,
		CreatedBy:   adminID,
		IsActive:    true,
	}
	if err := h.stores.Teams.Create(c.Request.Context(), team); err != nil {
		if database.IsUniqueViolation(err) {
			c.JSON(http.StatusConflict, gin.H{"error": "team name already exists"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create team"})
		return
	}

	c.JSON(http.StatusCreated, gin.H{"id": team.ID, "name": team.Name})
	AuditLog(h.stores.Audit, c, "team.create", "team", team.ID, map[string]interface{}{"name": team.Name})
}

// ── Admin: Get Team ─────────────────────────

func (h *TeamHandler) GetTeam(c *gin.Context) {
	teamID := c.Param("id")

	team, err := h.stores.Teams.GetByID(c.Request.Context(), teamID)
	if err == sql.ErrNoRows {
		c.JSON(http.StatusNotFound, gin.H{"error": "team not found"})
		return
	}
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
		return
	}

	c.JSON(http.StatusOK, team)
}

// ── Admin: Update Team ──────────────────────

func (h *TeamHandler) UpdateTeam(c *gin.Context) {
	teamID := c.Param("id")

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

	// Build fields map for store.Update (simple scalar fields)
	fields := map[string]interface{}{}
	if req.Name != nil {
		fields["name"] = *req.Name
	}
	if req.Description != nil {
		fields["description"] = *req.Description
	}
	if req.IsActive != nil {
		fields["is_active"] = *req.IsActive
	}

	hasFields := len(fields) > 0
	hasSettings := req.Settings != nil

	if !hasFields && !hasSettings {
		c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"})
		return
	}

	ctx := c.Request.Context()

	if hasFields {
		if err := h.stores.Teams.Update(ctx, teamID, fields); err != nil {
			if database.IsUniqueViolation(err) {
				c.JSON(http.StatusConflict, gin.H{"error": "team name already exists"})
				return
			}
			c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"})
			return
		}
	}

	if hasSettings {
		if err := h.stores.Teams.MergeSettings(ctx, teamID, *req.Settings); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "settings update failed"})
			return
		}
	}

	c.JSON(http.StatusOK, gin.H{"ok": true})
	AuditLog(h.stores.Audit, c, "team.update", "team", teamID, nil)
}

// ── Admin: Delete Team ──────────────────────

func (h *TeamHandler) DeleteTeam(c *gin.Context) {
	teamID := c.Param("id")
	ctx := c.Request.Context()

	// Verify existence
	if _, err := h.stores.Teams.GetByID(ctx, teamID); err != nil {
		if err == sql.ErrNoRows {
			c.JSON(http.StatusNotFound, gin.H{"error": "team not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed"})
		return
	}

	if err := h.stores.Teams.Delete(ctx, teamID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"ok": true})
	AuditLog(h.stores.Audit, c, "team.delete", "team", teamID, nil)
}

// ── Members: List ───────────────────────────

func (h *TeamHandler) ListMembers(c *gin.Context) {
	teamID := getTeamID(c)

	members, err := h.stores.Teams.ListMembers(c.Request.Context(), teamID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
		return
	}
	if members == nil {
		members = []models.TeamMember{}
	}

	c.JSON(http.StatusOK, gin.H{"data": members})
}

// ── Members: Add ────────────────────────────

func (h *TeamHandler) AddMember(c *gin.Context) {
	teamID := getTeamID(c)
	ctx := c.Request.Context()

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

	// Verify team exists
	exists, _ := h.stores.Teams.Exists(ctx, teamID)
	if !exists {
		c.JSON(http.StatusNotFound, gin.H{"error": "team not found"})
		return
	}

	// Verify user exists
	userExists, _ := h.stores.Users.Exists(ctx, req.UserID)
	if !userExists {
		c.JSON(http.StatusBadRequest, gin.H{"error": "user not found"})
		return
	}

	// Validate role against team's configured roles
	if err := validateTeamRole(ctx, h.stores, teamID, req.Role); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	id, err := h.stores.Teams.AddMemberReturningID(ctx, teamID, req.UserID, req.Role)
	if err != nil {
		if database.IsUniqueViolation(err) {
			c.JSON(http.StatusConflict, gin.H{"error": "user is already a member"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to add member"})
		return
	}

	c.JSON(http.StatusCreated, gin.H{"id": id})
	AuditLog(h.stores.Audit, c, "team.add_member", "team", getTeamID(c), map[string]interface{}{
		"user_id": req.UserID, "role": req.Role,
	})
}

// ── Members: Update Role ────────────────────

func (h *TeamHandler) UpdateMember(c *gin.Context) {
	teamID := getTeamID(c)
	memberID := c.Param("memberId")

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

	// Validate role against team's configured roles
	if err := validateTeamRole(c.Request.Context(), h.stores, teamID, req.Role); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	n, err := h.stores.Teams.UpdateMemberRoleByID(c.Request.Context(), memberID, teamID, req.Role)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"})
		return
	}
	if n == 0 {
		c.JSON(http.StatusNotFound, gin.H{"error": "member not found"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"ok": true})
	AuditLog(h.stores.Audit, c, "team.update_member", "team", getTeamID(c), map[string]interface{}{
		"member_id": memberID, "role": req.Role,
	})
}

// ── Members: Remove ─────────────────────────

func (h *TeamHandler) RemoveMember(c *gin.Context) {
	teamID := getTeamID(c)
	memberID := c.Param("memberId")

	n, err := h.stores.Teams.DeleteMemberByID(c.Request.Context(), memberID, teamID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "remove failed"})
		return
	}
	if n == 0 {
		c.JSON(http.StatusNotFound, gin.H{"error": "member not found"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"ok": true})
	AuditLog(h.stores.Audit, c, "team.remove_member", "team", getTeamID(c), map[string]interface{}{
		"member_id": memberID,
	})
}

// ── User: My Teams ──────────────────────────

func (h *TeamHandler) MyTeams(c *gin.Context) {
	userID := getUserID(c)

	teams, err := h.stores.Teams.ListForUser(c.Request.Context(), userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
		return
	}
	if teams == nil {
		teams = []models.Team{}
	}

	c.JSON(http.StatusOK, gin.H{"data": teams})
}

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

// getTeamID extracts team ID from either :id (admin routes) or :teamId (team-scoped routes).
func getTeamID(c *gin.Context) string {
	if id := c.Param("teamId"); id != "" {
		return id
	}
	return c.Param("id")
}

// ── Team Audit Log (scoped to team members) ─

func (h *TeamHandler) ListTeamAuditLog(c *gin.Context) {
	teamID := c.Param("teamId")
	page, perPage, offset := parsePagination(c)

	opts := store.AuditListOptions{
		ListOptions: store.ListOptions{
			Limit:  perPage,
			Offset: offset,
		},
		TeamID: teamID,
	}

	if action := c.Query("action"); action != "" {
		opts.Action = action
	}
	if actorID := c.Query("actor_id"); actorID != "" {
		opts.ActorID = actorID
	}
	if rt := c.Query("resource_type"); rt != "" {
		opts.ResourceType = rt
	}

	entries, total, err := h.stores.Audit.List(c.Request.Context(), opts)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
		return
	}
	if entries == nil {
		entries = []models.AuditEntry{}
	}

	c.JSON(http.StatusOK, gin.H{
		"data":     entries,
		"total":    total,
		"page":     page,
		"per_page": perPage,
	})
}

func (h *TeamHandler) ListTeamAuditActions(c *gin.Context) {
	teamID := c.Param("teamId")

	actions, err := h.stores.Teams.ListTeamAuditActions(c.Request.Context(), teamID)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{"actions": []string{}})
		return
	}

	c.JSON(http.StatusOK, gin.H{"actions": actions})
}

// ── Team Roles API (v0.3.4) ─────────────────

// builtinRoles are always present in every team's role list.
var builtinRoles = []string{"admin", "member"}

// ListRoles returns the team's configured roles (builtins + custom).
// GET /api/v1/teams/:teamId/roles
func (h *TeamHandler) ListRoles(c *gin.Context) {
	teamID := getTeamID(c)
	roles := getTeamRoles(c.Request.Context(), h.stores, teamID)
	c.JSON(http.StatusOK, gin.H{"data": roles})
}

// UpdateRoles replaces the team's roles array. Builtins (admin, member) are always included.
// PUT /api/v1/teams/:teamId/roles
func (h *TeamHandler) UpdateRoles(c *gin.Context) {
	teamID := getTeamID(c)

	var body struct {
		Roles []string `json:"roles" binding:"required"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Ensure builtins are present
	roleSet := map[string]bool{}
	for _, r := range builtinRoles {
		roleSet[r] = true
	}
	for _, r := range body.Roles {
		if r != "" && len(r) <= 50 {
			roleSet[r] = true
		}
	}
	roles := make([]string, 0, len(roleSet))
	for r := range roleSet {
		roles = append(roles, r)
	}

	rolesJSON, _ := json.Marshal(map[string]any{"roles": roles})
	if err := h.stores.Teams.MergeSettings(c.Request.Context(), teamID, string(rolesJSON)); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update roles"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"data": roles})
	AuditLog(h.stores.Audit, c, "team.update_roles", "team", teamID, map[string]interface{}{"roles": roles})
}

// getTeamRoles reads the roles array from team settings, falling back to builtins.
func getTeamRoles(ctx context.Context, stores store.Stores, teamID string) []string {
	team, err := stores.Teams.GetByID(ctx, teamID)
	if err != nil || team.Settings == nil {
		return builtinRoles
	}

	rolesRaw, ok := team.Settings["roles"]
	if !ok {
		return builtinRoles
	}

	// rolesRaw is []interface{} from JSONMap
	arr, ok := rolesRaw.([]interface{})
	if !ok {
		return builtinRoles
	}

	roles := make([]string, 0, len(arr))
	for _, v := range arr {
		if s, ok := v.(string); ok {
			roles = append(roles, s)
		}
	}
	if len(roles) == 0 {
		return builtinRoles
	}
	return roles
}

// validateTeamRole checks that a role string is valid for the given team.
func validateTeamRole(ctx context.Context, stores store.Stores, teamID, role string) error {
	roles := getTeamRoles(ctx, stores, teamID)
	for _, r := range roles {
		if r == role {
			return nil
		}
	}
	// Also allow builtins unconditionally (in case settings are empty)
	for _, r := range builtinRoles {
		if r == role {
			return nil
		}
	}
	return fmt.Errorf("role %q is not configured for this team", role)
}
