package middleware

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
)

// TelegramUserData represents the parsed Telegram user data
type TelegramUserData struct {
	ID        int64  `json:"id"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name,omitempty"`
	Username  string `json:"username,omitempty"`
	PhotoURL  string `json:"photo_url,omitempty"`
	AuthDate  int64  `json:"auth_date"`
	Hash      string `json:"hash"`
}

// AuthConfig holds configuration for the auth middleware
type AuthConfig struct {
	BotToken string
}

// AuthMiddleware creates a middleware for Telegram WebApp authentication
func AuthMiddleware(config AuthConfig) fiber.Handler {
	return func(c *fiber.Ctx) error {
		// Skip authentication for OPTIONS preflight requests
		if c.Method() == "OPTIONS" {
			return c.Next()
		}

		// Get initData from header
		initData := c.Get("X-Telegram-WebApp-Init-Data")
		if initData == "" {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
				"success": false,
				"message": "Missing Telegram init data",
			})
		}

		// Parse and validate the init data
		userData, err := ValidateTelegramInitData(initData, config.BotToken)
		if err != nil {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
				"success": false,
				"message": fmt.Sprintf("Invalid Telegram init data: %v", err),
			})
		}

		// Check if auth date is not too old (5 minutes)
		authTime := time.Unix(userData.AuthDate, 0)
		if time.Since(authTime) > 5*time.Minute {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
				"success": false,
				"message": "Auth data expired",
			})
		}

		// Store user data in context for handlers to use
		c.Locals("telegram_user", userData)

		return c.Next()
	}
}

// ValidateTelegramInitData validates Telegram WebApp init data
func ValidateTelegramInitData(initData, botToken string) (*TelegramUserData, error) {

	// Parse query parameters
	values, err := url.ParseQuery(initData)
	if err != nil {
		return nil, fmt.Errorf("failed to parse init data: %w", err)
	}


	// Extract hash
	hash := values.Get("hash")
	if hash == "" {
		return nil, errors.New("missing hash in init data")
	}

	// Build data check string
	var dataCheckStrings []string

	// Collect all parameters except hash
	for key, vals := range values {
		if key == "hash" {
			continue
		}
		for _, val := range vals {
			dataCheckStrings = append(dataCheckStrings, fmt.Sprintf("%s=%s", key, val))
		}
	}

	// Sort parameters alphabetically
	sort.Strings(dataCheckStrings)

	// Build data check string
	dataCheckString := strings.Join(dataCheckStrings, "\n")

	// Create secret key
	secretKey := hmac.New(sha256.New, []byte("WebAppData"))
	secretKey.Write([]byte(botToken))

	// Calculate HMAC
	h := hmac.New(sha256.New, secretKey.Sum(nil))
	h.Write([]byte(dataCheckString))
	calculatedHash := hex.EncodeToString(h.Sum(nil))

	// Compare hashes
	if calculatedHash != hash {
		return nil, errors.New("hash verification failed")
	}

	// Parse user data
	userData := &TelegramUserData{
		Hash:     hash,
		AuthDate: mustParseInt64(values.Get("auth_date")),
	}

	// Parse user field if present
	if userStr := values.Get("user"); userStr != "" {

		// Parse user JSON data
		var userMap map[string]interface{}
		if err := json.Unmarshal([]byte(userStr), &userMap); err != nil {
			return nil, fmt.Errorf("failed to parse user JSON data: %w", err)
		}

		// Extract user data from JSON
		if id, ok := userMap["id"].(float64); ok {
			userData.ID = int64(id)
		}
		if firstName, ok := userMap["first_name"].(string); ok {
			userData.FirstName = firstName
		}
		if lastName, ok := userMap["last_name"].(string); ok {
			userData.LastName = lastName
		}
		if username, ok := userMap["username"].(string); ok {
			userData.Username = username
		}
		if photoURL, ok := userMap["photo_url"].(string); ok {
			userData.PhotoURL = photoURL
		}

	} else {
	}

	return userData, nil
}

// mustParseInt64 helper function to parse int64
func mustParseInt64(s string) int64 {
	i, err := strconv.ParseInt(s, 10, 64)
	if err != nil {
		return 0
	}
	return i
}

// GetTelegramUser helper function to get user data from context
func GetTelegramUser(c *fiber.Ctx) *TelegramUserData {
	if user, ok := c.Locals("telegram_user").(*TelegramUserData); ok {
		return user
	}
	return nil
}

// RequireAuth is a convenience function that returns a handler requiring authentication
func RequireAuth(config AuthConfig) fiber.Handler {
	return AuthMiddleware(config)
}