package handlers

import (
	"strconv"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
	"github.com/tapper/backend/database"
	"github.com/tapper/backend/models"
	"github.com/tapper/backend/services"
	"gorm.io/gorm"
)

func ModSearchUsers(c *fiber.Ctx) error {
	query := c.Query("q", "")
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit, _ := strconv.Atoi(c.Query("limit", "20"))
	if page < 1 {
		page = 1
	}
	if limit < 1 || limit > 50 {
		limit = 20
	}
	offset := (page - 1) * limit

	db := database.DB.Model(&models.User{})
	if query != "" {
		search := "%" + strings.ToLower(query) + "%"
		db = db.Where("LOWER(username) LIKE ? OR LOWER(email) LIKE ? OR steam_id LIKE ?", search, search, search)
	}

	var total int64
	db.Count(&total)

	var users []models.User
	db.Order("created_at DESC").Offset(offset).Limit(limit).Find(&users)

	return c.JSON(fiber.Map{
		"users": users,
		"total": total,
		"page":  page,
		"limit": limit,
	})
}

func ModGetUser(c *fiber.Ctx) error {
	userID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid user ID",
		})
	}

	var user models.User
	if err := database.DB.First(&user, userID).Error; err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": "User not found",
		})
	}

	var bans []models.Ban
	database.DB.Where("user_id = ?", userID).Preload("Issuer").Order("created_at DESC").Find(&bans)

	var penalties []models.Penalty
	database.DB.Where("user_id = ?", userID).Order("created_at DESC").Find(&penalties)

	var notes []models.StaffNote
	database.DB.Where("user_id = ?", userID).Preload("Author").Order("created_at DESC").Find(&notes)

	return c.JSON(fiber.Map{
		"user":      user,
		"bans":      bans,
		"penalties": penalties,
		"notes":     notes,
	})
}

type banRequest struct {
	Type         string  `json:"type"`
	Reason       string  `json:"reason"`
	InternalNote *string `json:"internal_note,omitempty"`
	Evidence     *string `json:"evidence,omitempty"`
	Duration     *string `json:"duration,omitempty"`
}

func ModBanUser(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	targetID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid user ID",
		})
	}

	var req banRequest
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid request body",
		})
	}

	validTypes := map[string]bool{
		models.BanTypeWarning: true,
		models.BanTypeTemp:    true,
		models.BanTypePerm:    true,
		models.BanTypeGame:    true,
		models.BanTypeFull:    true,
	}
	if !validTypes[req.Type] {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid ban type",
		})
	}

	if strings.TrimSpace(req.Reason) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Reason is required",
		})
	}

	if actorID == targetID {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Cannot ban yourself",
		})
	}

	var target models.User
	if err := database.DB.First(&target, targetID).Error; err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": "User not found",
		})
	}

	var actor models.User
	database.DB.First(&actor, actorID)
	if target.RoleLevel() >= actor.RoleLevel() {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
			"error": "Cannot ban a user with equal or higher role",
		})
	}

	now := time.Now().UTC()
	ban := models.Ban{
		UserID:       targetID,
		IssuedBy:     actorID,
		Type:         req.Type,
		Reason:       req.Reason,
		InternalNote: req.InternalNote,
		Evidence:     req.Evidence,
		Duration:     req.Duration,
		StartsAt:     now,
	}

	if req.Duration != nil && *req.Duration != "" && req.Type != models.BanTypePerm {
		duration, err := parseDuration(*req.Duration)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
				"error": "Invalid duration format. Use: 1h, 24h, 7d, 30d",
			})
		}
		expires := now.Add(duration)
		ban.ExpiresAt = &expires
	}

	if err := database.DB.Create(&ban).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to create ban",
		})
	}

	services.LogAudit(actorID, models.AuditBanUser, "user", &targetID, map[string]interface{}{
		"ban_type": req.Type,
		"reason":   req.Reason,
		"duration": req.Duration,
	}, c.IP())

	return c.Status(fiber.StatusCreated).JSON(ban)
}

func ModUnbanUser(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	targetID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid user ID",
		})
	}

	var req struct {
		Reason string `json:"reason"`
	}
	if err := c.BodyParser(&req); err != nil || strings.TrimSpace(req.Reason) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Revocation reason is required",
		})
	}

	now := time.Now().UTC()
	result := database.DB.Model(&models.Ban{}).
		Where("user_id = ? AND revoked = false AND type != ? AND (expires_at IS NULL OR expires_at > ?)",
			targetID, models.BanTypeWarning, now).
		Updates(map[string]interface{}{
			"revoked":       true,
			"revoked_by":    actorID,
			"revoked_at":    now,
			"revoke_reason": req.Reason,
		})

	if result.RowsAffected == 0 {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": "No active ban found for this user",
		})
	}

	services.LogAudit(actorID, models.AuditUnbanUser, "user", &targetID, map[string]interface{}{
		"reason": req.Reason,
	}, c.IP())

	return c.JSON(fiber.Map{
		"message":  "Ban revoked",
		"affected": result.RowsAffected,
	})
}

func ModWarnUser(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	targetID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid user ID",
		})
	}

	var req struct {
		Reason string `json:"reason"`
	}
	if err := c.BodyParser(&req); err != nil || strings.TrimSpace(req.Reason) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Warning reason is required",
		})
	}

	now := time.Now().UTC()
	ban := models.Ban{
		UserID:   targetID,
		IssuedBy: actorID,
		Type:     models.BanTypeWarning,
		Reason:   req.Reason,
		StartsAt: now,
	}

	if err := database.DB.Create(&ban).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to issue warning",
		})
	}

	services.LogAudit(actorID, models.AuditWarnUser, "user", &targetID, map[string]interface{}{
		"reason": req.Reason,
	}, c.IP())

	return c.Status(fiber.StatusCreated).JSON(ban)
}

func ModAddNote(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	targetID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid user ID",
		})
	}

	var req struct {
		Content string `json:"content"`
	}
	if err := c.BodyParser(&req); err != nil || strings.TrimSpace(req.Content) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Note content is required",
		})
	}

	note := models.StaffNote{
		UserID:   targetID,
		AuthorID: actorID,
		Content:  req.Content,
	}

	if err := database.DB.Create(&note).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to add note",
		})
	}

	services.LogAudit(actorID, models.AuditAddNote, "user", &targetID, nil, c.IP())

	return c.Status(fiber.StatusCreated).JSON(note)
}

func ModGetUserHistory(c *fiber.Ctx) error {
	targetID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid user ID",
		})
	}

	var bans []models.Ban
	database.DB.Where("user_id = ?", targetID).Preload("Issuer").Order("created_at DESC").Find(&bans)

	var notes []models.StaffNote
	database.DB.Where("user_id = ?", targetID).Preload("Author").Order("created_at DESC").Find(&notes)

	var penalties []models.Penalty
	database.DB.Where("user_id = ?", targetID).Order("created_at DESC").Find(&penalties)

	return c.JSON(fiber.Map{
		"bans":      bans,
		"notes":     notes,
		"penalties": penalties,
	})
}

func ModListTickets(c *fiber.Ctx) error {
	status := c.Query("status", "")
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit, _ := strconv.Atoi(c.Query("limit", "20"))
	if page < 1 {
		page = 1
	}
	if limit < 1 || limit > 50 {
		limit = 20
	}
	offset := (page - 1) * limit

	db := database.DB.Model(&models.Ticket{})
	if status != "" {
		db = db.Where("status = ?", status)
	} else {
		db = db.Where("status NOT IN (?)", []string{models.TicketStatusClosed})
	}

	var total int64
	db.Count(&total)

	var tickets []models.Ticket
	db.Preload("User").Preload("Assignee").
		Order("priority ASC, updated_at DESC").
		Offset(offset).Limit(limit).
		Find(&tickets)

	return c.JSON(fiber.Map{
		"tickets": tickets,
		"total":   total,
		"page":    page,
		"limit":   limit,
	})
}

func ModGetTicket(c *fiber.Ctx) error {
	ticketID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid ticket ID",
		})
	}

	var ticket models.Ticket
	if err := database.DB.Preload("User").Preload("Assignee").First(&ticket, ticketID).Error; err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": "Ticket not found",
		})
	}

	var messages []models.TicketMessage
	database.DB.Where("ticket_id = ?", ticketID).
		Preload("Author").
		Order("created_at ASC").
		Find(&messages)

	return c.JSON(fiber.Map{
		"ticket":   ticket,
		"messages": messages,
	})
}

func ModReplyTicket(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	ticketID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid ticket ID",
		})
	}

	var req struct {
		Content    string `json:"content"`
		IsInternal bool   `json:"is_internal"`
	}
	if err := c.BodyParser(&req); err != nil || strings.TrimSpace(req.Content) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Message content is required",
		})
	}

	msg, err := services.AddTicketMessage(ticketID, actorID, req.Content, req.IsInternal)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to add message",
		})
	}

	if !req.IsInternal {
		database.DB.Model(&models.Ticket{}).Where("id = ?", ticketID).
			Updates(map[string]interface{}{
				"status":     models.TicketStatusWaitingOnUser,
				"updated_at": time.Now().UTC(),
			})
	}

	services.LogAudit(actorID, models.AuditTicketAction, "ticket", &ticketID, map[string]interface{}{
		"action": "reply",
	}, c.IP())

	return c.Status(fiber.StatusCreated).JSON(msg)
}

func ModAssignTicket(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	ticketID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid ticket ID",
		})
	}

	var req struct {
		AssigneeID *uuid.UUID `json:"assignee_id"`
	}
	c.BodyParser(&req)

	assigneeID := actorID
	if req.AssigneeID != nil {
		assigneeID = *req.AssigneeID
	}

	if err := services.AssignTicket(ticketID, assigneeID); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to assign ticket",
		})
	}

	services.LogAudit(actorID, models.AuditTicketAction, "ticket", &ticketID, map[string]interface{}{
		"action":   "assign",
		"assignee": assigneeID,
	}, c.IP())

	return c.JSON(fiber.Map{"message": "Ticket assigned"})
}

func ModCloseTicket(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	ticketID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid ticket ID",
		})
	}

	if err := services.CloseTicket(ticketID); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to close ticket",
		})
	}

	services.LogAudit(actorID, models.AuditTicketAction, "ticket", &ticketID, map[string]interface{}{
		"action": "close",
	}, c.IP())

	return c.JSON(fiber.Map{"message": "Ticket closed"})
}

func ModEscalateTicket(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	ticketID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid ticket ID",
		})
	}

	if err := services.EscalateTicket(ticketID); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to escalate ticket",
		})
	}

	services.LogAudit(actorID, models.AuditTicketAction, "ticket", &ticketID, map[string]interface{}{
		"action": "escalate",
	}, c.IP())

	return c.JSON(fiber.Map{"message": "Ticket escalated"})
}

func ModListMatches(c *fiber.Ctx) error {
	status := c.Query("status", "")
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit, _ := strconv.Atoi(c.Query("limit", "20"))
	if page < 1 {
		page = 1
	}
	if limit < 1 || limit > 50 {
		limit = 20
	}
	offset := (page - 1) * limit

	db := database.DB.Model(&models.Match{})
	if status != "" {
		db = db.Where("status = ?", status)
	}

	var total int64
	db.Count(&total)

	var matches []models.Match
	db.Order("created_at DESC").Offset(offset).Limit(limit).Find(&matches)

	return c.JSON(fiber.Map{
		"matches": matches,
		"total":   total,
		"page":    page,
		"limit":   limit,
	})
}

func ModCancelMatch(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	matchID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid match ID",
		})
	}

	var req struct {
		Reason string `json:"reason"`
	}
	if err := c.BodyParser(&req); err != nil || strings.TrimSpace(req.Reason) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Cancellation reason is required",
		})
	}

	var match models.Match
	if err := database.DB.First(&match, matchID).Error; err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": "Match not found",
		})
	}

	activeStatuses := map[string]bool{
		models.MatchStatusMatched:           true,
		models.MatchStatusServerStarting:    true,
		models.MatchStatusWaitingForPlayers: true,
		models.MatchStatusKnifeRound:        true,
		models.MatchStatusSideSelection:     true,
		models.MatchStatusLive:              true,
	}
	if !activeStatuses[match.Status] {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Match is not in an active state",
		})
	}

	now := time.Now().UTC()
	database.DB.Model(&match).Updates(map[string]interface{}{
		"status":      models.MatchStatusCancelled,
		"finished_at": now,
	})

	services.PublishEvent("match_events", map[string]interface{}{
		"match_id": matchID.String(),
		"event":    "status_change",
		"status":   "cancelled",
		"reason":   req.Reason,
	})

	services.LogAudit(actorID, models.AuditCancelMatch, "match", &matchID, map[string]interface{}{
		"reason": req.Reason,
	}, c.IP())

	return c.JSON(fiber.Map{"message": "Match cancelled"})
}

func ModNullifyMatch(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	matchID, err := uuid.Parse(c.Params("id"))
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid match ID",
		})
	}

	var req struct {
		Reason string `json:"reason"`
	}
	if err := c.BodyParser(&req); err != nil || strings.TrimSpace(req.Reason) == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Nullification reason is required",
		})
	}

	var match models.Match
	if err := database.DB.First(&match, matchID).Error; err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": "Match not found",
		})
	}

	if match.Status != models.MatchStatusFinished {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Can only nullify finished matches",
		})
	}

	var mmrEntries []models.MMRHistory
	database.DB.Where("match_id = ?", matchID).Find(&mmrEntries)

	for _, entry := range mmrEntries {
		database.DB.Model(&models.User{}).Where("id = ?", entry.UserID).
			Update("mmr", entry.OldMMR)

		if entry.Delta > 0 {
			database.DB.Model(&models.User{}).Where("id = ?", entry.UserID).
				Update("wins", gorm.Expr("GREATEST(wins - 1, 0)"))
		} else if entry.Delta < 0 {
			database.DB.Model(&models.User{}).Where("id = ?", entry.UserID).
				Update("losses", gorm.Expr("GREATEST(losses - 1, 0)"))
		}
		database.DB.Model(&models.User{}).Where("id = ?", entry.UserID).
			Update("matches_played", gorm.Expr("GREATEST(matches_played - 1, 0)"))
	}

	database.DB.Model(&match).Update("status", "nullified")
	database.DB.Model(&models.MatchPlayer{}).Where("match_id = ?", matchID).
		Updates(map[string]interface{}{
			"result":    "nullified",
			"mmr_delta": 0,
		})

	services.LogAudit(actorID, models.AuditNullifyMatch, "match", &matchID, map[string]interface{}{
		"reason":        req.Reason,
		"players_affected": len(mmrEntries),
	}, c.IP())

	return c.JSON(fiber.Map{
		"message":          "Match nullified, MMR changes reverted",
		"players_affected": len(mmrEntries),
	})
}

// parseDuration accepts Go duration strings plus a "Nd" suffix for days.
func parseDuration(s string) (time.Duration, error) {
	s = strings.TrimSpace(s)
	if strings.HasSuffix(s, "d") {
		days, err := strconv.Atoi(s[:len(s)-1])
		if err != nil {
			return 0, err
		}
		return time.Duration(days) * 24 * time.Hour, nil
	}
	return time.ParseDuration(s)
}
