package middleware

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gothinkster/golang-gin-realworld-example-app/config"
)

// AuthResponse represents the response from auth center
type AuthResponse struct {
	Success bool        `json:"success"`
	Message string      `json:"message"`
	Data    AuthData    `json:"data"`
}

type AuthData struct {
	Email      string     `json:"email"`
	Roles      []string   `json:"roles"`
	Status     string     `json:"status"`
	TokenInfo  TokenInfo  `json:"tokenInfo"`
	UserID     int64      `json:"userID"`
	Username   string     `json:"username"`
}

type TokenInfo struct {
	Exp string `json:"exp"`
	Iat string `json:"iat"`
	Jti string `json:"jti"`
}

// UserContext represents the user data stored in gin context
type UserContext struct {
	UserID   int64    `json:"user_id"`
	Username string   `json:"username"`
	Email    string   `json:"email"`
	Roles    []string `json:"roles"`
}

// Simple in-memory cache (in production, use Redis)
var tokenCache = make(map[string]*CacheEntry)

type CacheEntry struct {
	UserData  *UserContext
	ExpiresAt time.Time
}

// hashToken creates SHA256 hash of token
func hashToken(token string) string {
	hash := sha256.Sum256([]byte(token))
	return hex.EncodeToString(hash[:])
}

// validateTokenWithAuthCenter validates token with auth center
func validateTokenWithAuthCenter(token string) (*AuthData, error) {
	cfg := config.AppConfig
	url := fmt.Sprintf("%s/api/auth/validate-token", cfg.AuthCenterURL)

	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("auth service unavailable: %v", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %v", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("auth failed: %s", string(body))
	}

	var authResp AuthResponse
	if err := json.Unmarshal(body, &authResp); err != nil {
		return nil, fmt.Errorf("failed to parse response: %v", err)
	}

	if !authResp.Success {
		return nil, fmt.Errorf("token validation failed: %s", authResp.Message)
	}

	return &authResp.Data, nil
}

// AuthMiddleware validates JWT token from auth center
func AuthMiddleware(required bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		// Extract token from Authorization header
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			if required {
				c.JSON(http.StatusUnauthorized, gin.H{
					"success": false,
					"message": "Authorization header required",
				})
				c.Abort()
				return
			}
			c.Next()
			return
		}

		// Parse Bearer token
		parts := strings.SplitN(authHeader, " ", 2)
		if len(parts) != 2 || parts[0] != "Bearer" {
			if required {
				c.JSON(http.StatusUnauthorized, gin.H{
					"success": false,
					"message": "Invalid authorization header format",
				})
				c.Abort()
				return
			}
			c.Next()
			return
		}

		token := parts[1]
		tokenHash := hashToken(token)

		// Check cache first
		if cached, exists := tokenCache[tokenHash]; exists {
			if time.Now().Before(cached.ExpiresAt) {
				// Cache hit and not expired
				c.Set("user", cached.UserData)
				c.Next()
				return
			}
			// Cache expired, remove it
			delete(tokenCache, tokenHash)
		}

		// Validate with auth center
		authData, err := validateTokenWithAuthCenter(token)
		if err != nil {
			if required {
				c.JSON(http.StatusUnauthorized, gin.H{
					"success": false,
					"message": "Token validation failed",
					"error":   err.Error(),
				})
				c.Abort()
				return
			}
			c.Next()
			return
		}

		// Create user context
		userCtx := &UserContext{
			UserID:   authData.UserID,
			Username: authData.Username,
			Email:    authData.Email,
			Roles:    authData.Roles,
		}

		// Cache the result (5 minutes)
		tokenCache[tokenHash] = &CacheEntry{
			UserData:  userCtx,
			ExpiresAt: time.Now().Add(5 * time.Minute),
		}

		// Set user in context
		c.Set("user", userCtx)
		c.Next()
	}
}

// GetCurrentUser retrieves user from context
func GetCurrentUser(c *gin.Context) (*UserContext, bool) {
	userVal, exists := c.Get("user")
	if !exists {
		return nil, false
	}

	user, ok := userVal.(*UserContext)
	return user, ok
}

// RequireAdmin middleware to check admin role
func RequireAdmin() gin.HandlerFunc {
	return func(c *gin.Context) {
		user, exists := GetCurrentUser(c)
		if !exists {
			c.JSON(http.StatusUnauthorized, gin.H{
				"success": false,
				"message": "Authentication required",
			})
			c.Abort()
			return
		}

		// Check if user has admin role
		isAdmin := false
		for _, role := range user.Roles {
			if role == "admin" {
				isAdmin = true
				break
			}
		}

		if !isAdmin {
			c.JSON(http.StatusForbidden, gin.H{
				"success": false,
				"message": "Admin access required",
			})
			c.Abort()
			return
		}

		c.Next()
	}
}
