package middleware

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	"api_yal/internal/logger"

	"github.com/golang-jwt/jwt/v5"
	"go.uber.org/zap"
)

type contextKey string

const (
	UserIDKey    contextKey = "userID"
	UserEmailKey contextKey = "userEmail"
	UserRoleKey  contextKey = "userRole"
)

// AuthMiddleware создает middleware для проверки JWT токена
func AuthMiddleware(jwtSecret string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			l := logger.Get()
			
			// Логируем только в debug режиме, чтобы не засорять логи
			l.Debug("Auth middleware: processing request", 
				zap.String("path", r.URL.Path),
				zap.String("method", r.Method))

			// Получаем токен из заголовка Authorization
			authHeader := r.Header.Get("Authorization")
			
			if authHeader == "" {
				l.Debug("Authorization header missing")
				http.Error(w, "Authorization header required", http.StatusUnauthorized)
				return
			}

			// Ожидаем формат "Bearer <token>"
			parts := strings.Split(authHeader, " ")
			if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
				l.Debug("Invalid authorization header format", 
					zap.String("header", authHeader))
				http.Error(w, "Invalid authorization header format", http.StatusUnauthorized)
				return
			}

			tokenString := parts[1]

			// Парсим и валидируем токен
			claims, err := validateToken(tokenString, jwtSecret)
			if err != nil {
				l.Debug("Token validation failed", zap.Error(err))
				
				// Возвращаем разные сообщения в зависимости от ошибки
				if err == jwt.ErrTokenExpired {
					http.Error(w, "Token expired", http.StatusUnauthorized)
					return
				}
				http.Error(w, "Invalid token", http.StatusUnauthorized)
				return
			}

			// Добавляем информацию о пользователе в контекст
			ctx := addUserToContext(r.Context(), claims)
			
			// Логируем успешную аутентификацию (только в debug)
			if userID, ok := ctx.Value(UserIDKey).(uint); ok {
				l.Debug("User authenticated", 
					zap.Uint("user_id", userID),
					zap.String("path", r.URL.Path))
			}

			// Передаем управление дальше с обновленным контекстом
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

// validateToken валидирует JWT токен и возвращает claims
func validateToken(tokenString, jwtSecret string) (jwt.MapClaims, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		// Проверяем метод подписи
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return []byte(jwtSecret), nil
	})

	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, fmt.Errorf("invalid token")
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, fmt.Errorf("invalid claims")
	}

	// Проверяем тип токена (должен быть access)
	if tokenType, exists := claims["type"]; !exists || tokenType != "access" {
		return nil, fmt.Errorf("invalid token type, expected access")
	}

	return claims, nil
}

// addUserToContext добавляет информацию о пользователе в контекст
func addUserToContext(ctx context.Context, claims jwt.MapClaims) context.Context {
	// Извлекаем userID из sub (subject)
	if userIDStr, ok := claims["sub"].(string); ok {
		var userID uint
		if _, err := fmt.Sscan(userIDStr, &userID); err == nil {
			ctx = context.WithValue(ctx, UserIDKey, userID)
		}
	}
	
	// Извлекаем email
	if email, ok := claims["email"].(string); ok {
		ctx = context.WithValue(ctx, UserEmailKey, email)
	}
	
	// Извлекаем роль
	if role, ok := claims["role"].(string); ok {
		ctx = context.WithValue(ctx, UserRoleKey, role)
	}
	
	return ctx
}

// GetUserID извлекает ID пользователя из контекста
func GetUserID(ctx context.Context) (uint, bool) {
	userID, ok := ctx.Value(UserIDKey).(uint)
	return userID, ok
}

// GetUserEmail извлекает email пользователя из контекста
func GetUserEmail(ctx context.Context) (string, bool) {
	email, ok := ctx.Value(UserEmailKey).(string)
	return email, ok
}

// GetUserRole извлекает роль пользователя из контекста
func GetUserRole(ctx context.Context) (string, bool) {
	role, ok := ctx.Value(UserRoleKey).(string)
	return role, ok
}