package handlers

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"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"
)

const steamOpenIDURL = "https://steamcommunity.com/openid/login"

func SteamRedirect(publicURL, frontendURL string) fiber.Handler {
	return func(c *fiber.Ctx) error {
		_, ok := c.Locals("user_id").(uuid.UUID)
		if !ok {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
				"error": "Authentication required to link Steam account",
			})
		}

		returnURL := publicURL + "/api/auth/steam/callback"
		realm := publicURL

		params := url.Values{
			"openid.ns":         {"http://specs.openid.net/auth/2.0"},
			"openid.mode":       {"checkid_setup"},
			"openid.return_to":  {returnURL},
			"openid.realm":      {realm},
			"openid.identity":   {"http://specs.openid.net/auth/2.0/identifier_select"},
			"openid.claimed_id": {"http://specs.openid.net/auth/2.0/identifier_select"},
		}

		redirectURL := steamOpenIDURL + "?" + params.Encode()

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

func SteamCallback(steamAPIKey, frontendURL string) fiber.Handler {
	return func(c *fiber.Ctx) error {
		userID, ok := c.Locals("user_id").(uuid.UUID)
		if !ok {
			token := c.Query("token")
			if token != "" {
				claims, err := services.ValidateToken(token)
				if err == nil {
					userID = claims.UserID
					ok = true
				}
			}
			if !ok {
				return c.Redirect(frontendURL + "/#/profile?steam=error&reason=not_authenticated")
			}
		}

		mode := c.Query("openid.mode")
		if mode != "id_res" {
			return c.Redirect(frontendURL + "/#/profile?steam=error&reason=invalid_mode")
		}

		verifyParams := url.Values{}
		for key, values := range c.Queries() {
			if strings.HasPrefix(key, "openid.") {
				verifyParams.Set(key, values)
			}
		}
		verifyParams.Set("openid.mode", "check_authentication")

		resp, err := http.PostForm(steamOpenIDURL, verifyParams)
		if err != nil {
			return c.Redirect(frontendURL + "/#/profile?steam=error&reason=verification_failed")
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return c.Redirect(frontendURL + "/#/profile?steam=error&reason=read_failed")
		}

		if !strings.Contains(string(body), "is_valid:true") {
			return c.Redirect(frontendURL + "/#/profile?steam=error&reason=invalid_claim")
		}

		claimedID := c.Query("openid.claimed_id")
		steamID := extractSteamID(claimedID)
		if steamID == "" {
			return c.Redirect(frontendURL + "/#/profile?steam=error&reason=no_steam_id")
		}

		var existingUser models.User
		if err := database.DB.Where("steam_id = ? AND id != ?", steamID, userID).First(&existingUser).Error; err == nil {
			return c.Redirect(frontendURL + "/#/profile?steam=error&reason=already_linked")
		}

		personaName, avatarURL := fetchSteamProfile(steamAPIKey, steamID)

		now := time.Now().UTC()
		updates := map[string]interface{}{
			"steam_id":        steamID,
			"steam_linked_at": now,
			"updated_at":      now,
		}
		if personaName != "" {
			updates["steam_persona_name"] = personaName
		}
		if avatarURL != "" {
			updates["steam_avatar_url"] = avatarURL
		}

		if err := database.DB.Model(&models.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil {
			return c.Redirect(frontendURL + "/#/profile?steam=error&reason=db_error")
		}

		return c.Redirect(frontendURL + "/#/profile?steam=linked")
	}
}

func extractSteamID(claimedID string) string {
	prefix := "https://steamcommunity.com/openid/id/"
	if strings.HasPrefix(claimedID, prefix) {
		return claimedID[len(prefix):]
	}
	return ""
}

func SteamDevLink(environment, steamAPIKey string) fiber.Handler {
	return func(c *fiber.Ctx) error {
		if environment != "development" {
			return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
				"error": "Dev linking is only available in development mode",
			})
		}

		userID, ok := c.Locals("user_id").(uuid.UUID)
		if !ok {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
				"error": "Authentication required",
			})
		}

		var body struct {
			SteamID string `json:"steam_id"`
		}
		if err := c.BodyParser(&body); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
				"error": "Invalid request body",
			})
		}

		steamID := strings.TrimSpace(body.SteamID)
		if len(steamID) != 17 || !strings.HasPrefix(steamID, "7656") {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
				"error": "Invalid Steam ID 64. Must be a 17-digit number starting with 7656.",
			})
		}

		var existingUser models.User
		if err := database.DB.Where("steam_id = ? AND id != ?", steamID, userID).First(&existingUser).Error; err == nil {
			return c.Status(fiber.StatusConflict).JSON(fiber.Map{
				"error": "This Steam account is already linked to another user",
			})
		}

		personaName, avatarURL := fetchSteamProfile(steamAPIKey, steamID)

		now := time.Now().UTC()
		updates := map[string]interface{}{
			"steam_id":        steamID,
			"steam_linked_at": now,
			"updated_at":      now,
		}
		if personaName != "" {
			updates["steam_persona_name"] = personaName
		}
		if avatarURL != "" {
			updates["steam_avatar_url"] = avatarURL
		}

		if err := database.DB.Model(&models.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
				"error": "Failed to link Steam account",
			})
		}

		var user models.User
		database.DB.First(&user, "id = ?", userID)

		return c.JSON(fiber.Map{
			"message":     "Steam account linked successfully (dev mode)",
			"steam_id":    steamID,
			"persona_name": personaName,
			"avatar_url":  avatarURL,
		})
	}
}

func fetchSteamProfile(apiKey, steamID string) (personaName, avatarURL string) {
	if apiKey == "" {
		return "", ""
	}

	url := fmt.Sprintf(
		"https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key=%s&steamids=%s",
		apiKey, steamID,
	)

	resp, err := http.Get(url)
	if err != nil {
		return "", ""
	}
	defer resp.Body.Close()

	var result struct {
		Response struct {
			Players []struct {
				PersonaName string `json:"personaname"`
				AvatarFull  string `json:"avatarfull"`
			} `json:"players"`
		} `json:"response"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return "", ""
	}

	if len(result.Response.Players) > 0 {
		p := result.Response.Players[0]
		return p.PersonaName, p.AvatarFull
	}

	return "", ""
}
