package handlers

import (
	"log"
	"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 GetMatchConfig(publicHost string) fiber.Handler {
	return func(c *fiber.Ctx) error {
		matchID, err := uuid.Parse(c.Params("match_id"))
		if err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
				"error": "Invalid match ID",
			})
		}

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

		var players []models.MatchPlayer
		database.DB.Where("match_id = ?", matchID).Preload("User").Find(&players)

		team1Players := make(map[string]string)
		team2Players := make(map[string]string)

		for _, p := range players {
			if p.User.SteamID == nil {
				continue
			}
			if p.Team == 1 {
				team1Players[*p.User.SteamID] = p.User.Username
			} else {
				team2Players[*p.User.SteamID] = p.User.Username
			}
		}

		config := fiber.Map{
			"matchid": match.ID.String(),
			"team1": fiber.Map{
				"name":    match.Team1Name,
				"players": team1Players,
			},
			"team2": fiber.Map{
				"name":    match.Team2Name,
				"players": team2Players,
			},
			"num_maps":         1,
			"maplist":          []string{match.Map},
			"map_sides":        []string{"knife"},
			"clinch_series":    true,
			"players_per_team": 5,
			"cvars": fiber.Map{
				"hostname": "tapper Match #" + match.ID.String()[:8],
			},
		}

		return c.JSON(config)
	}
}

func HandleMatchEvents(c *fiber.Ctx) error {
	var event struct {
		Event   string          `json:"event"`
		MatchID string          `json:"matchid"`
		Data    interface{}     `json:"data"`
		Params  matchEventParams `json:"params"`
	}

	if err := c.BodyParser(&event); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid event payload",
		})
	}

	matchID, err := uuid.Parse(event.MatchID)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid match ID",
		})
	}

	log.Printf("[internal] Match event: %s for match %s", event.Event, event.MatchID[:8])

	switch event.Event {
	case "going_live":
		return handleGoingLive(c, matchID)
	case "round_end":
		return handleRoundEnd(c, matchID, event.Params)
	case "player_connect":
		return handlePlayerConnect(c, matchID, event.Params)
	case "player_disconnect":
		return handlePlayerDisconnect(c, matchID, event.Params)
	case "map_result":
		return handleMapResult(c, matchID, event.Params)
	default:
		log.Printf("[internal] Unknown event type: %s", event.Event)
		return c.JSON(fiber.Map{"status": "ignored"})
	}
}

type matchEventParams struct {
	Team1Score int                    `json:"team1_score"`
	Team2Score int                    `json:"team2_score"`
	Round      int                    `json:"round"`
	SteamID    string                 `json:"steam_id"`
	Winner     string                 `json:"winner"`
	Players    map[string]playerStats `json:"players"`
}

type playerStats struct {
	Kills        int `json:"kills"`
	Deaths       int `json:"deaths"`
	Assists      int `json:"assists"`
	Headshots    int `json:"headshots"`
	Damage       int `json:"damage"`
	RoundsPlayed int `json:"rounds_played"`
	MVPs         int `json:"mvps"`
}

func handleGoingLive(c *fiber.Ctx, matchID uuid.UUID) error {
	now := time.Now().UTC()
	database.DB.Model(&models.Match{}).Where("id = ?", matchID).
		Updates(map[string]interface{}{
			"status":     models.MatchStatusLive,
			"started_at": now,
		})

	services.PublishEvent("match_events", map[string]interface{}{
		"match_id": matchID.String(),
		"event":    "status_change",
		"status":   "live",
		"previous": "waiting_for_players",
	})

	return c.JSON(fiber.Map{"status": "ok"})
}

func handleRoundEnd(c *fiber.Ctx, matchID uuid.UUID, params matchEventParams) error {
	database.DB.Model(&models.Match{}).Where("id = ?", matchID).
		Updates(map[string]interface{}{
			"team1_score": params.Team1Score,
			"team2_score": params.Team2Score,
		})

	if params.Players != nil {
		updatePlayerStats(matchID, params.Players)
	}

	services.PublishEvent("match_events", map[string]interface{}{
		"match_id":    matchID.String(),
		"event":       "score_update",
		"team1_score": params.Team1Score,
		"team2_score": params.Team2Score,
		"round":       params.Round,
	})

	return c.JSON(fiber.Map{"status": "ok"})
}

func handlePlayerConnect(c *fiber.Ctx, matchID uuid.UUID, params matchEventParams) error {
	services.PublishEvent("match_events", map[string]interface{}{
		"match_id": matchID.String(),
		"event":    "player_connect",
		"steam_id": params.SteamID,
	})
	return c.JSON(fiber.Map{"status": "ok"})
}

func handlePlayerDisconnect(c *fiber.Ctx, matchID uuid.UUID, params matchEventParams) error {
	services.PublishEvent("match_events", map[string]interface{}{
		"match_id": matchID.String(),
		"event":    "player_disconnect",
		"steam_id": params.SteamID,
	})
	return c.JSON(fiber.Map{"status": "ok"})
}

func handleMapResult(c *fiber.Ctx, matchID uuid.UUID, params matchEventParams) error {
	now := time.Now().UTC()

	database.DB.Model(&models.Match{}).Where("id = ?", matchID).
		Updates(map[string]interface{}{
			"status":      models.MatchStatusFinished,
			"team1_score": params.Team1Score,
			"team2_score": params.Team2Score,
			"finished_at": now,
		})

	if params.Players != nil {
		updatePlayerStats(matchID, params.Players)
	}

	var match models.Match
	database.DB.First(&match, matchID)

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

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

	results, err := services.CalculateElo(&match, players, stats)
	if err != nil {
		log.Printf("[internal] Failed to calculate Elo: %v", err)
	} else {
		if err := services.ApplyEloResults(matchID, results); err != nil {
			log.Printf("[internal] Failed to apply Elo results: %v", err)
		}
	}

	services.PublishEvent("match_events", map[string]interface{}{
		"match_id":    matchID.String(),
		"event":       "match_end",
		"winner":      params.Winner,
		"team1_score": params.Team1Score,
		"team2_score": params.Team2Score,
	})

	return c.JSON(fiber.Map{"status": "ok"})
}

func updatePlayerStats(matchID uuid.UUID, players map[string]playerStats) {
	for steamID, stats := range players {
		var user models.User
		if err := database.DB.Where("steam_id = ?", steamID).First(&user).Error; err != nil {
			continue
		}

		var existing models.MatchPlayerStats
		result := database.DB.Where("match_id = ? AND user_id = ?", matchID, user.ID).First(&existing)
		if result.Error != nil {
			database.DB.Create(&models.MatchPlayerStats{
				MatchID:      matchID,
				UserID:       user.ID,
				Kills:        stats.Kills,
				Deaths:       stats.Deaths,
				Assists:      stats.Assists,
				Headshots:    stats.Headshots,
				Damage:       stats.Damage,
				RoundsPlayed: stats.RoundsPlayed,
				MVPs:         stats.MVPs,
			})
		} else {
			database.DB.Model(&existing).Updates(map[string]interface{}{
				"kills":         stats.Kills,
				"deaths":        stats.Deaths,
				"assists":       stats.Assists,
				"headshots":     stats.Headshots,
				"damage":        stats.Damage,
				"rounds_played": stats.RoundsPlayed,
				"mvps":          stats.MVPs,
			})
		}
	}
}

// HandleDemoUpload accepts but discards demo uploads, exists so MatchZy doesn't error on upload.
func HandleDemoUpload(c *fiber.Ctx) error {
	log.Printf("[internal] Demo upload received, size: %d bytes", len(c.Body()))
	return c.JSON(fiber.Map{"status": "ok"})
}
