package auth

import (
	"fmt"
	"os"
	"strings"
	"time"

	"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
)

var jwtSecret = []byte(os.Getenv("JWT_SECRET"))

func GenerateJWT(user storage.User) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{
		"id":         user.ID,
		"email":      user.Email,
		"login":      user.GithubLogin,
		"github_id":  user.GithubID,
		"avatar_url": user.AvatarURL,
		"exp":        time.Now().Add(30 * 24 * time.Hour).Unix(), // 30 дней
		"iat":        time.Now().Unix(),
	})
	tokenString, err := token.SignedString(jwtSecret)
	if err != nil {
		return "", err
	}
	return tokenString, nil
}

func JWTMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		auth := c.GetHeader("Authorization")
		if !strings.HasPrefix(auth, "Bearer ") {
			c.AbortWithStatusJSON(401, gin.H{"error": "Bearer required"})
			return
		}

		tokenString := strings.TrimPrefix(auth, "Bearer ")
		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 jwtSecret, nil
		})

		if err != nil || !token.Valid {
			c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
			return
		}

		claims, ok := token.Claims.(jwt.MapClaims)
		if !ok {
			c.AbortWithStatusJSON(401, gin.H{"error": "invalid claims"})
			return
		}

		idValue, idExists := claims["id"]
		if !idExists {
			c.AbortWithStatusJSON(401, gin.H{"error": "missing id in token"})
			return
		}

		idFloat, ok := idValue.(float64)
		if !ok {
			c.AbortWithStatusJSON(401, gin.H{"error": "invalid id type in token"})
			return
		}

		githubIDValue, githubExists := claims["github_id"]
		if !githubExists {
			c.AbortWithStatusJSON(401, gin.H{"error": "missing github_id in token"})
			return
		}

		githubIDFloat, ok := githubIDValue.(float64)
		if !ok {
			c.AbortWithStatusJSON(401, gin.H{"error": "invalid github_id type in token"})
			return
		}

		loginValue, loginExists := claims["login"]
		if !loginExists {
			c.AbortWithStatusJSON(401, gin.H{"error": "missing login in token"})
			return
		}

		login, ok := loginValue.(string)
		if !ok {
			c.AbortWithStatusJSON(401, gin.H{"error": "invalid login type in token"})
			return
		}

		c.Set("user_id", int(idFloat))
		c.Set("github_id", int(githubIDFloat))
		c.Set("login", login)
		c.Next()
	}
}

func RequireAdmin() gin.HandlerFunc {
	return func(c *gin.Context) {
		githubID, exists := c.Get("github_id")
		if !exists {
			c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
			return
		}

		id := githubID.(int)
		if id != 173489813 {
			c.AbortWithStatusJSON(403, gin.H{"error": "access denied"})
			return
		}

		c.Next()
	}
}

func ValidateJWT(tokenString string) (*storage.User, 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 jwtSecret, nil
	})

	if err != nil || !token.Valid {
		return nil, err
	}

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

	if exp, ok := claims["exp"].(float64); ok {
		if time.Now().Unix() > int64(exp) {
			return nil, fmt.Errorf("token expired")
		}
	}
	idFloat, ok := claims["id"].(float64)
	if !ok {
		return nil, fmt.Errorf("invalid id in token")
	}

	githubIDFloat, ok := claims["github_id"].(float64)
	if !ok {
		return nil, fmt.Errorf("invalid github_id in token")
	}

	login, ok := claims["login"].(string)
	if !ok {
		return nil, fmt.Errorf("invalid login in token")
	}

	email, ok := claims["email"].(string)
	if !ok {
		return nil, fmt.Errorf("invalid email in token")
	}

	avatarURL, _ := claims["avatar_url"].(string)

	user := &storage.User{
		ID:          int(idFloat),
		GithubID:    int(githubIDFloat),
		GithubLogin: login,
		Email:       email,
		AvatarURL:   avatarURL,
	}

	return user, nil
}
