package api

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

	"git.warky.dev/wdevs/whatshooked/pkg/config"
	"git.warky.dev/wdevs/whatshooked/pkg/storage"
	"github.com/bitechdev/ResolveSpec/pkg/security"
	"github.com/golang-jwt/jwt/v5"
	"github.com/uptrace/bun"
	"golang.org/x/crypto/bcrypt"
)

// SecurityProvider implements ResolveSpec SecurityProvider interface
type SecurityProvider struct {
	jwtSecret   []byte
	userRepo    *storage.UserRepository
	sessionRepo *storage.SessionRepository
	config      *config.Config // Add config for Phase 1 auth
}

// NewSecurityProvider creates a new security provider
func NewSecurityProvider(jwtSecret string, db *bun.DB, cfg *config.Config) security.SecurityProvider {
	return &SecurityProvider{
		jwtSecret:   []byte(jwtSecret),
		userRepo:    storage.NewUserRepository(db),
		sessionRepo: storage.NewSessionRepository(db),
		config:      cfg,
	}
}

// Claims represents JWT claims
type Claims struct {
	UserID   string `json:"user_id"` // Changed from int to string for UUID support
	Username string `json:"username"`
	Role     string `json:"role"`
	jwt.RegisteredClaims
}

// GenerateToken generates a JWT token
func (sp *SecurityProvider) GenerateToken(userID string, username, role string) (string, error) {
	expirationTime := time.Now().Add(24 * time.Hour)

	claims := &Claims{
		UserID:   userID,
		Username: username,
		Role:     role,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(expirationTime),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			NotBefore: jwt.NewNumericDate(time.Now()),
			Issuer:    "whatshooked",
			Subject:   userID,
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(sp.jwtSecret)
}

// ValidateToken validates a JWT token and returns the claims
func (sp *SecurityProvider) ValidateToken(tokenString string) (*Claims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &Claims{}, 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 sp.jwtSecret, nil
	})

	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*Claims); ok && token.Valid {
		return claims, nil
	}

	return nil, fmt.Errorf("invalid token")
}

// Login authenticates a user (implements security.Authenticator)
func (sp *SecurityProvider) Login(ctx context.Context, req security.LoginRequest) (*security.LoginResponse, error) {
	// Get user by username
	user, err := sp.userRepo.GetByUsername(ctx, req.Username)
	if err != nil {
		return nil, fmt.Errorf("invalid credentials")
	}

	// Check if user is active
	if !user.Active {
		return nil, fmt.Errorf("user is inactive")
	}

	// Verify password
	if err := bcrypt.CompareHashAndPassword([]byte(user.Password.String()), []byte(req.Password)); err != nil {
		return nil, fmt.Errorf("invalid credentials")
	}

	// Generate JWT token
	token, err := sp.GenerateToken(user.ID.String(), req.Username, user.Role.String())
	if err != nil {
		return nil, fmt.Errorf("failed to generate token: %w", err)
	}

	// Build user context
	userCtx := &security.UserContext{
		UserID:   0, // Keep as 0 for compatibility, actual ID is in claims
		UserName: req.Username,
		Email:    user.Email.String(),
		Roles:    []string{user.Role.String()},
		Claims: map[string]any{
			"role":    user.Role.String(),
			"user_id": user.ID.String(), // Store actual UUID here
		},
	}

	return &security.LoginResponse{
		Token:     token,
		User:      userCtx,
		ExpiresIn: int64(24 * time.Hour.Seconds()),
	}, nil
}

// Logout logs out a user (implements security.Authenticator)
func (sp *SecurityProvider) Logout(ctx context.Context, req security.LogoutRequest) error {
	// For JWT, we can implement token blacklisting if needed
	// For now, just return success (JWT will expire naturally)
	return nil
}

// Authenticate authenticates an HTTP request (implements security.Authenticator)
func (sp *SecurityProvider) Authenticate(r *http.Request) (*security.UserContext, error) {
	// Try JWT authentication first
	token := extractBearerToken(r)
	if token != "" {
		claims, err := sp.ValidateToken(token)
		if err == nil {
			// Get user from database
			user, err := sp.userRepo.GetByID(r.Context(), claims.UserID)
			if err == nil && user.Active {
				return &security.UserContext{
					UserID:   0, // Keep as 0 for compatibility
					UserName: claims.Username,
					Email:    user.Email.String(),
					Roles:    []string{user.Role.String()},
					Claims: map[string]any{
						"role":    user.Role.String(),
						"user_id": claims.UserID, // Store actual UUID here
					},
				}, nil
			}
		}
	}

	// Try Phase 1 authentication (API key, basic auth)
	if sp.validatePhase1Auth(r) {
		// Create a generic user context for Phase 1 auth
		// Use username from config or "api-user" if using API key
		username := "api-user"
		if sp.config.Server.Username != "" {
			username = sp.config.Server.Username
		}

		// Check if using basic auth to get actual username
		if basicUser, _, ok := r.BasicAuth(); ok && basicUser != "" {
			username = basicUser
		}

		return &security.UserContext{
			UserID:   0, // No user ID for Phase 1 auth
			UserName: username,
			Email:    "",
			Roles:    []string{"admin"}, // Phase 1 auth gets admin role
			Claims: map[string]any{
				"role":   "admin",
				"legacy": true, // Mark as legacy auth
			},
		}, nil
	}

	return nil, fmt.Errorf("authentication failed")
}

// validatePhase1Auth checks Phase 1 authentication (API key or basic auth)
func (sp *SecurityProvider) validatePhase1Auth(r *http.Request) bool {
	// Check if any Phase 1 authentication is configured
	hasAuth := sp.config.Server.AuthKey != "" ||
		sp.config.Server.Username != "" ||
		sp.config.Server.Password != ""

	if !hasAuth {
		// No Phase 1 authentication configured
		return false
	}

	// Check for API key authentication (x-api-key header)
	if sp.config.Server.AuthKey != "" {
		apiKey := r.Header.Get("x-api-key")
		if apiKey == sp.config.Server.AuthKey {
			return true
		}

		// Also check Authorization header for bearer token (API key)
		authHeader := r.Header.Get("Authorization")
		if len(authHeader) > 7 && authHeader[:7] == "Bearer " {
			token := authHeader[7:]
			if token == sp.config.Server.AuthKey {
				return true
			}
		}
	}

	// Check for username/password authentication (HTTP Basic Auth)
	if sp.config.Server.Username != "" && sp.config.Server.Password != "" {
		username, password, ok := r.BasicAuth()
		if ok && username == sp.config.Server.Username && password == sp.config.Server.Password {
			return true
		}
	}

	return false
}

// GetColumnSecurity returns column security rules (implements security.ColumnSecurityProvider)
func (sp *SecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
	// Return empty - no column-level security for now
	return []security.ColumnSecurity{}, nil
}

// GetRowSecurity returns row security rules (implements security.RowSecurityProvider)
func (sp *SecurityProvider) GetRowSecurity(ctx context.Context, userID int, schema, table string) (security.RowSecurity, error) {
	// If userID is 0, it's JWT auth with UUID - allow admin access for now
	if userID == 0 {
		return security.RowSecurity{
			Template: "", // Empty template means no filtering (admin access)
		}, nil
	}

	// Get user to check role
	user, err := sp.userRepo.GetByID(ctx, fmt.Sprintf("%d", userID))
	if err != nil {
		return security.RowSecurity{}, err
	}

	// Admin can access all rows
	if user.Role.String() == "admin" {
		return security.RowSecurity{
			Template: "", // Empty template means no filtering
		}, nil
	}

	// Regular users can only access their own data
	// Apply user_id filter for tables that have user_id column
	userTables := []string{"api_keys", "hooks", "whatsapp_accounts"}
	for _, userTable := range userTables {
		if table == userTable {
			return security.RowSecurity{
				Template: fmt.Sprintf("user_id = %d", userID),
			}, nil
		}
	}

	// For other tables, no additional filtering
	return security.RowSecurity{
		Template: "",
	}, nil
}

// extractBearerToken extracts Bearer token from Authorization header
func extractBearerToken(r *http.Request) string {
	auth := r.Header.Get("Authorization")
	if len(auth) > 7 && strings.HasPrefix(auth, "Bearer ") {
		return auth[7:]
	}
	return ""
}
