package services

import (
	"context"
	"fmt"
	"strings"
	"time"

	"go.uber.org/zap"

	"github.com/RyanCopley/skybridge/kms/internal/auth"
	"github.com/RyanCopley/skybridge/kms/internal/config"
	"github.com/RyanCopley/skybridge/kms/internal/domain"
	"github.com/RyanCopley/skybridge/kms/internal/errors"
	"github.com/RyanCopley/skybridge/kms/internal/repository"
)

// authenticationService implements the AuthenticationService interface
type authenticationService struct {
	config         config.ConfigProvider
	logger         *zap.Logger
	jwtManager     *auth.JWTManager
	permissionRepo repository.PermissionRepository
}

// NewAuthenticationService creates a new authentication service
func NewAuthenticationService(config config.ConfigProvider, logger *zap.Logger, permissionRepo repository.PermissionRepository) AuthenticationService {
	jwtManager := auth.NewJWTManager(config, logger)
	return &authenticationService{
		config:         config,
		logger:         logger,
		jwtManager:     jwtManager,
		permissionRepo: permissionRepo,
	}
}

// GetUserID extracts user ID from context
func (s *authenticationService) GetUserID(ctx context.Context) (string, error) {
	// For now, this is a simple implementation
	// In a real implementation, this would extract from JWT tokens, session, etc.
	
	if userID, ok := ctx.Value("user_id").(string); ok {
		return userID, nil
	}
	
	return "", fmt.Errorf("user ID not found in context")
}

// ValidatePermissions checks if user has required permissions
func (s *authenticationService) ValidatePermissions(ctx context.Context, userID string, appID string, requiredPermissions []string) error {
	s.logger.Debug("Validating permissions", 
		zap.String("user_id", userID), 
		zap.String("app_id", appID), 
		zap.Strings("required_permissions", requiredPermissions))

	// Implement role-based permission validation
	userRoles := s.getUserRoles(userID)
	
	// Check each required permission
	for _, requiredPerm := range requiredPermissions {
		hasPermission := false
		
		// Check if user has the permission directly through role mapping
		for _, role := range userRoles {
			if s.roleHasPermission(role, requiredPerm) {
				hasPermission = true
				break
			}
		}
		
		// If not found through roles, check direct permission grants
		if !hasPermission {
			hasPermission = s.hasDirectPermission(ctx, userID, requiredPerm)
		}
		
		if !hasPermission {
			s.logger.Warn("User lacks required permission",
				zap.String("user_id", userID),
				zap.String("required_permission", requiredPerm),
				zap.Strings("user_roles", userRoles))
			return fmt.Errorf("insufficient permissions: missing '%s'", requiredPerm)
		}
	}
	
	s.logger.Debug("Permission validation successful",
		zap.String("user_id", userID),
		zap.Strings("required_permissions", requiredPermissions),
		zap.Strings("user_roles", userRoles))
	
	return nil
}

// GetUserClaims retrieves user claims
func (s *authenticationService) GetUserClaims(ctx context.Context, userID string) (map[string]string, error) {
	s.logger.Debug("Getting user claims", zap.String("user_id", userID))

	// Implement actual claims retrieval
	claims := make(map[string]string)
	
	// Set basic user claims
	claims["user_id"] = userID
	claims["subject"] = userID
	
	// Extract name from email if userID is an email
	if strings.Contains(userID, "@") {
		claims["email"] = userID
		namePart := strings.Split(userID, "@")[0]
		claims["preferred_username"] = namePart
		// Convert underscores/dots to spaces for display name
		displayName := strings.ReplaceAll(strings.ReplaceAll(namePart, "_", " "), ".", " ")
		claims["name"] = displayName
	} else {
		claims["preferred_username"] = userID
		claims["name"] = userID
	}
	
	// Add role-based claims
	userRoles := s.getUserRoles(userID)
	if len(userRoles) > 0 {
		claims["roles"] = strings.Join(userRoles, ",")
		claims["primary_role"] = userRoles[0]
	}
	
	// Add environment-specific claims
	claims["provider"] = "internal"
	claims["auth_method"] = "header"
	claims["issued_at"] = fmt.Sprintf("%d", time.Now().Unix())
	
	return claims, nil
}

// getUserRoles retrieves roles for a user based on patterns and rules
func (s *authenticationService) getUserRoles(userID string) []string {
	var roles []string
	
	// Role assignment based on email patterns and business rules
	userLower := strings.ToLower(userID)
	
	// Super admin roles
	if strings.Contains(userLower, "admin@") || strings.Contains(userLower, "superadmin") {
		roles = append(roles, "super_admin")
		return roles // Super admins get all permissions
	}
	
	// Admin roles
	if strings.Contains(userLower, "admin") {
		roles = append(roles, "admin")
	}
	
	// Developer roles
	if strings.Contains(userLower, "dev") || strings.Contains(userLower, "engineer") || strings.Contains(userLower, "tech") {
		roles = append(roles, "developer")
	}
	
	// Manager roles
	if strings.Contains(userLower, "manager") || strings.Contains(userLower, "lead") {
		roles = append(roles, "manager")
	}
	
	// Default role for all users
	if len(roles) == 0 {
		roles = append(roles, "viewer")
	}
	
	return roles
}

// roleHasPermission checks if a role has a specific permission
func (s *authenticationService) roleHasPermission(role, permission string) bool {
	// Define role-based permission matrix
	rolePermissions := map[string][]string{
		"super_admin": {
			"internal.*", "app.*", "token.*", "repo.*", "permission.*", "admin.*",
		},
		"admin": {
			"app.*", "token.*", "permission.read", "permission.list", "repo.read", "repo.write",
		},
		"developer": {
			"app.read", "app.list", "token.create", "token.read", "token.list", "repo.*",
		},
		"manager": {
			"app.read", "app.list", "token.read", "token.list", "repo.read", "permission.read",
		},
		"viewer": {
			"app.read", "repo.read", "token.read",
		},
	}
	
	permissions, exists := rolePermissions[role]
	if !exists {
		return false
	}
	
	// Check for exact match or wildcard match
	for _, perm := range permissions {
		if perm == permission {
			return true
		}
		
		// Check wildcard permissions (e.g., "app.*" matches "app.read")
		if strings.HasSuffix(perm, "*") {
			prefix := strings.TrimSuffix(perm, "*")
			if strings.HasPrefix(permission, prefix) {
				return true
			}
		}
		
		// Check hierarchical permissions (e.g., "repo" includes "repo.read")
		if !strings.Contains(perm, ".") && strings.HasPrefix(permission, perm+".") {
			return true
		}
	}
	
	return false
}

// hasDirectPermission checks if user has direct permission grant
func (s *authenticationService) hasDirectPermission(ctx context.Context, userID, permission string) bool {
	// This would typically query the database for direct user permissions
	// For now, implement basic logic
	
	// Check for system-level permissions that might be granted to specific users
	if permission == "internal.system" && strings.Contains(userID, "system") {
		return true
	}
	
	// In a real system, this would query the granted_permissions table
	// or a user_permissions table for direct grants
	return false
}

// ValidateJWTToken validates a JWT token and returns claims
func (s *authenticationService) ValidateJWTToken(ctx context.Context, tokenString string) (*domain.AuthContext, error) {
	s.logger.Debug("Validating JWT token")

	// Validate the token using JWT manager
	claims, err := s.jwtManager.ValidateToken(tokenString)
	if err != nil {
		s.logger.Warn("JWT token validation failed", zap.Error(err))
		return nil, err
	}

	// Check if token is revoked
	revoked, err := s.jwtManager.IsTokenRevoked(tokenString)
	if err != nil {
		s.logger.Error("Failed to check token revocation status", zap.Error(err))
		return nil, errors.NewInternalError("Failed to validate token").WithInternal(err)
	}

	if revoked {
		s.logger.Warn("JWT token is revoked", zap.String("user_id", claims.UserID))
		return nil, errors.NewAuthenticationError("Token has been revoked")
	}

	// Convert JWT claims to AuthContext
	authContext := &domain.AuthContext{
		UserID:      claims.UserID,
		TokenType:   claims.TokenType,
		Permissions: claims.Permissions,
		Claims:      claims.Claims,
		AppID:       claims.AppID,
	}

	s.logger.Debug("JWT token validated successfully", 
		zap.String("user_id", claims.UserID),
		zap.String("app_id", claims.AppID))

	return authContext, nil
}

// GenerateJWTToken generates a new JWT token for a user
func (s *authenticationService) GenerateJWTToken(ctx context.Context, userToken *domain.UserToken) (string, error) {
	s.logger.Debug("Generating JWT token", 
		zap.String("user_id", userToken.UserID),
		zap.String("app_id", userToken.AppID))

	// Generate the token using JWT manager
	tokenString, err := s.jwtManager.GenerateToken(userToken)
	if err != nil {
		s.logger.Error("Failed to generate JWT token", zap.Error(err))
		return "", err
	}

	s.logger.Debug("JWT token generated successfully", 
		zap.String("user_id", userToken.UserID),
		zap.String("app_id", userToken.AppID))

	return tokenString, nil
}

// RefreshJWTToken refreshes an existing JWT token
func (s *authenticationService) RefreshJWTToken(ctx context.Context, tokenString string, newExpiration time.Time) (string, error) {
	s.logger.Debug("Refreshing JWT token")

	// Refresh the token using JWT manager
	newTokenString, err := s.jwtManager.RefreshToken(tokenString, newExpiration)
	if err != nil {
		s.logger.Error("Failed to refresh JWT token", zap.Error(err))
		return "", err
	}

	s.logger.Debug("JWT token refreshed successfully")

	return newTokenString, nil
}
