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"
)

func AdminListUsers(c *fiber.Ctx) error {
	query := c.Query("q", "")
	roleFilter := c.Query("role", "")
	tierFilter := c.Query("tier", "")
	banFilter := c.Query("banned", "")
	sortBy := c.Query("sort", "created_at")
	sortDir := c.Query("dir", "desc")
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit, _ := strconv.Atoi(c.Query("limit", "20"))
	if page < 1 {
		page = 1
	}
	if limit < 1 || limit > 100 {
		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)
	}
	if roleFilter != "" {
		db = db.Where("role = ?", roleFilter)
	}
	if tierFilter != "" {
		db = db.Where("tier = ?", tierFilter)
	}

	allowedSorts := map[string]bool{
		"created_at": true, "mmr": true, "username": true,
		"matches_played": true, "wins": true,
	}
	if !allowedSorts[sortBy] {
		sortBy = "created_at"
	}
	if sortDir != "asc" {
		sortDir = "desc"
	}

	var total int64
	db.Count(&total)

	var users []models.User
	db.Order(sortBy + " " + sortDir).Offset(offset).Limit(limit).Find(&users)

	if banFilter == "true" {
		var filtered []models.User
		for _, u := range users {
			if ban := services.GetActiveBan(u.ID); ban != nil {
				filtered = append(filtered, u)
			}
		}
		users = filtered
	}

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

type adminEditUserRequest struct {
	Role *string `json:"role,omitempty"`
	Tier *string `json:"tier,omitempty"`
	MMR  *int    `json:"mmr,omitempty"`
}

func AdminEditUser(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 adminEditUserRequest
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid request body",
		})
	}

	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",
		})
	}

	updates := map[string]interface{}{}
	details := map[string]interface{}{}

	if req.Role != nil {
		validRoles := map[string]bool{"user": true, "moderator": true, "admin": true, "owner": true}
		if !validRoles[*req.Role] {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
				"error": "Invalid role",
			})
		}
		actorRole := c.Locals("role").(string)
		if *req.Role == "owner" && actorRole != "owner" {
			return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
				"error": "Only the owner can promote to owner",
			})
		}
		updates["role"] = *req.Role
		details["old_role"] = target.Role
		details["new_role"] = *req.Role
	}

	if req.Tier != nil {
		validTiers := map[string]bool{"normal": true, "premium": true, "vip": true}
		if !validTiers[*req.Tier] {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
				"error": "Invalid tier",
			})
		}
		updates["tier"] = *req.Tier
		details["old_tier"] = target.Tier
		details["new_tier"] = *req.Tier
	}

	if req.MMR != nil {
		oldMMR := target.MMR
		updates["mmr"] = *req.MMR
		details["old_mmr"] = oldMMR
		details["new_mmr"] = *req.MMR

		database.DB.Create(&models.MMRHistory{
			UserID: targetID,
			OldMMR: oldMMR,
			NewMMR: *req.MMR,
			Delta:  *req.MMR - oldMMR,
			Reason: models.MMRReasonAdminOverride,
		})
	}

	if len(updates) == 0 {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "No updates provided",
		})
	}

	updates["updated_at"] = time.Now().UTC()
	if err := database.DB.Model(&target).Updates(updates).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to update user",
		})
	}

	services.LogAudit(actorID, models.AuditEditUser, "user", &targetID, details, c.IP())

	database.DB.First(&target, targetID)
	return c.JSON(target)
}

func AdminUnlinkSteam(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 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",
		})
	}

	if target.SteamID == nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "User has no linked Steam account",
		})
	}

	oldSteamID := *target.SteamID
	now := time.Now().UTC()
	database.DB.Model(&target).Updates(map[string]interface{}{
		"steam_id":           nil,
		"steam_persona_name": nil,
		"steam_avatar_url":   nil,
		"steam_linked_at":    nil,
		"updated_at":         now,
	})

	services.LogAudit(actorID, models.AuditUnlinkSteam, "user", &targetID, map[string]interface{}{
		"old_steam_id": oldSteamID,
	}, c.IP())

	return c.JSON(fiber.Map{
		"message":      "Steam account unlinked",
		"old_steam_id": oldSteamID,
	})
}

func AdminChangeTier(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 {
		Tier string `json:"tier"`
	}
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid request body",
		})
	}

	validTiers := map[string]bool{"normal": true, "premium": true, "vip": true}
	if !validTiers[req.Tier] {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid tier. Must be: normal, premium, or vip",
		})
	}

	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",
		})
	}

	oldTier := target.Tier
	database.DB.Model(&target).Updates(map[string]interface{}{
		"tier":       req.Tier,
		"updated_at": time.Now().UTC(),
	})

	services.LogAudit(actorID, models.AuditChangeTier, "user", &targetID, map[string]interface{}{
		"old_tier": oldTier,
		"new_tier": req.Tier,
	}, c.IP())

	return c.JSON(fiber.Map{
		"message":  "Tier updated",
		"old_tier": oldTier,
		"new_tier": req.Tier,
	})
}

func AdminChangeRole(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)
	actorRole := c.Locals("role").(string)
	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 {
		Role string `json:"role"`
	}
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid request body",
		})
	}

	validRoles := map[string]bool{"user": true, "moderator": true, "admin": true}
	if actorRole == "owner" {
		validRoles["owner"] = true
	}
	if !validRoles[req.Role] {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid role",
		})
	}

	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() && actorID != targetID {
		return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
			"error": "Cannot change the role of a user with equal or higher role",
		})
	}

	oldRole := target.Role
	database.DB.Model(&target).Updates(map[string]interface{}{
		"role":       req.Role,
		"updated_at": time.Now().UTC(),
	})

	services.LogAudit(actorID, models.AuditChangeRole, "user", &targetID, map[string]interface{}{
		"old_role": oldRole,
		"new_role": req.Role,
	}, c.IP())

	return c.JSON(fiber.Map{
		"message":  "Role updated",
		"old_role": oldRole,
		"new_role": req.Role,
	})
}

func AdminGetEloConfig(c *fiber.Ctx) error {
	cfg, err := services.GetEloConfig()
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to load Elo configuration",
		})
	}
	return c.JSON(cfg)
}

func AdminUpdateEloConfig(c *fiber.Ctx) error {
	actorID := c.Locals("user_id").(uuid.UUID)

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

	cfg.ID = 1
	now := time.Now().UTC()
	cfg.UpdatedAt = &now
	cfg.UpdatedBy = &actorID

	if err := database.DB.Save(&cfg).Error; err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to update Elo configuration",
		})
	}

	services.LogAudit(actorID, models.AuditEditEloConfig, "config", nil, cfg, c.IP())

	return c.JSON(cfg)
}

func AdminGetServers(c *fiber.Ctx) error {
	var servers []models.GameServer
	database.DB.Order("created_at DESC").Find(&servers)

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

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

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

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

	database.DB.Model(&models.PortPool{}).Where("server_id = ?", serverID).
		Updates(map[string]interface{}{
			"allocated": false,
			"server_id": nil,
		})

	services.LogAudit(actorID, "stop_server", "server", &serverID, nil, c.IP())

	return c.JSON(fiber.Map{"message": "Server stopped"})
}

func AdminGetStats(c *fiber.Ctx) error {
	var totalUsers, activeUsers, totalMatches, openTickets, activeBans int64
	now := time.Now().UTC()
	weekAgo := now.Add(-7 * 24 * time.Hour)

	database.DB.Model(&models.User{}).Count(&totalUsers)
	database.DB.Model(&models.User{}).Where("last_match_at > ?", weekAgo).Count(&activeUsers)
	database.DB.Model(&models.Match{}).Count(&totalMatches)
	database.DB.Model(&models.Ticket{}).Where("status NOT IN (?)", []string{models.TicketStatusClosed}).Count(&openTickets)
	database.DB.Model(&models.Ban{}).
		Where("revoked = false AND type != ? AND (expires_at IS NULL OR expires_at > ?)", models.BanTypeWarning, now).
		Count(&activeBans)

	type mmrBucket struct {
		Bracket string `json:"bracket"`
		Count   int64  `json:"count"`
	}
	var mmrDist []mmrBucket
	database.DB.Raw(`
		SELECT
			CASE
				WHEN mmr < 800 THEN 'Bronze'
				WHEN mmr < 1200 THEN 'Silver'
				WHEN mmr < 1600 THEN 'Gold'
				WHEN mmr < 2000 THEN 'Platinum'
				WHEN mmr < 2400 THEN 'Diamond'
				WHEN mmr < 2800 THEN 'Master'
				WHEN mmr < 3200 THEN 'Grandmaster'
				ELSE 'Champion'
			END AS bracket,
			COUNT(*) AS count
		FROM users
		WHERE placement_done = true
		GROUP BY bracket
		ORDER BY MIN(mmr)
	`).Scan(&mmrDist)

	type dailyCount struct {
		Date  string `json:"date"`
		Count int64  `json:"count"`
	}
	var matchesPerDay []dailyCount
	database.DB.Raw(`
		SELECT DATE(created_at) AS date, COUNT(*) AS count
		FROM matches
		WHERE created_at > ?
		GROUP BY DATE(created_at)
		ORDER BY date
	`, weekAgo).Scan(&matchesPerDay)

	type tierCount struct {
		Tier  string `json:"tier"`
		Count int64  `json:"count"`
	}
	var tierDist []tierCount
	database.DB.Raw(`
		SELECT tier, COUNT(*) AS count
		FROM users
		GROUP BY tier
	`).Scan(&tierDist)

	var playersInQueue int64
	database.DB.Model(&models.QueueEntry{}).Count(&playersInQueue)

	var activeMatches int64
	database.DB.Model(&models.Match{}).Where("status IN (?)",
		[]string{models.MatchStatusLive, models.MatchStatusWaitingForPlayers, models.MatchStatusServerStarting}).
		Count(&activeMatches)

	return c.JSON(fiber.Map{
		"total_users":      totalUsers,
		"active_users":     activeUsers,
		"total_matches":    totalMatches,
		"open_tickets":     openTickets,
		"active_bans":      activeBans,
		"players_in_queue": playersInQueue,
		"active_matches":   activeMatches,
		"mmr_distribution": mmrDist,
		"matches_per_day":  matchesPerDay,
		"tier_distribution": tierDist,
	})
}

func AdminGetAuditLog(c *fiber.Ctx) error {
	action := c.Query("action", "")
	actorFilter := c.Query("actor_id", "")
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit, _ := strconv.Atoi(c.Query("limit", "50"))
	if page < 1 {
		page = 1
	}
	if limit < 1 || limit > 100 {
		limit = 50
	}
	offset := (page - 1) * limit

	db := database.DB.Model(&models.AuditLog{})
	if action != "" {
		db = db.Where("action = ?", action)
	}
	if actorFilter != "" {
		if actorID, err := uuid.Parse(actorFilter); err == nil {
			db = db.Where("actor_id = ?", actorID)
		}
	}

	var total int64
	db.Count(&total)

	var logs []models.AuditLog
	db.Preload("Actor").Order("created_at DESC").Offset(offset).Limit(limit).Find(&logs)

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