package services

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"veza-backend-api/internal/database"

	"github.com/google/uuid"
	"go.uber.org/zap"
)

// AuditService gère les logs d'audit
type AuditService struct {
	db     *database.Database
	logger *zap.Logger
}

// AuditLog représente un log d'audit
type AuditLog struct {
	ID         uuid.UUID       `json:"id" db:"id"`
	UserID     *uuid.UUID      `json:"user_id" db:"user_id"`
	Action     string          `json:"action" db:"action"`
	Resource   string          `json:"resource" db:"resource"`
	ResourceID *uuid.UUID      `json:"resource_id" db:"resource_id"`
	IPAddress  string          `json:"ip_address" db:"ip_address"`
	UserAgent  string          `json:"user_agent" db:"user_agent"`
	Metadata   json.RawMessage `json:"metadata" db:"metadata"`
	Timestamp  time.Time       `json:"timestamp" db:"timestamp"`
}

// AuditLogCreateRequest données pour créer un log d'audit
type AuditLogCreateRequest struct {
	UserID     *uuid.UUID             `json:"user_id"`
	Action     string                 `json:"action"`
	Resource   string                 `json:"resource"`
	ResourceID *uuid.UUID             `json:"resource_id"`
	IPAddress  string                 `json:"ip_address"`
	UserAgent  string                 `json:"user_agent"`
	Metadata   map[string]interface{} `json:"metadata"`
}

// AuditLogSearchRequest paramètres de recherche
// BE-API-034: Enhanced with better filtering and pagination
type AuditLogSearchRequest struct {
	UserID     *uuid.UUID `json:"user_id"`
	Action     string     `json:"action"`
	Resource   string     `json:"resource"`
	ResourceID *uuid.UUID `json:"resource_id"`
	IPAddress  string     `json:"ip_address"`
	UserAgent  string     `json:"user_agent"`
	StartDate  *time.Time `json:"start_date"`
	EndDate    *time.Time `json:"end_date"`
	Limit      int        `json:"limit"`
	Offset     int        `json:"offset"`
	Page       int        `json:"page"` // Alternative to offset (page-based pagination)
}

// AuditStats statistiques d'audit
type AuditStats struct {
	Action      string `json:"action" db:"action"`
	Resource    string `json:"resource" db:"resource"`
	ActionCount int64  `json:"action_count" db:"action_count"`
	UniqueUsers int64  `json:"unique_users" db:"unique_users"`
	UniqueIPs   int64  `json:"unique_ips" db:"unique_ips"`
}

// SuspiciousActivity activité suspecte détectée
type SuspiciousActivity struct {
	UserID        *uuid.UUID `json:"user_id" db:"user_id"`
	IPAddress     string     `json:"ip_address" db:"ip_address"`
	ActionCount   int64      `json:"action_count" db:"action_count"`
	UniqueActions int64      `json:"unique_actions" db:"unique_actions"`
	RiskScore     int        `json:"risk_score" db:"risk_score"`
}

// NewAuditService crée un nouveau service d'audit
func NewAuditService(db *database.Database, logger *zap.Logger) *AuditService {
	return &AuditService{
		db:     db,
		logger: logger,
	}
}

// LogAction enregistre une action d'audit
func (as *AuditService) LogAction(ctx context.Context, req *AuditLogCreateRequest) error {
	// Convertir les métadonnées en JSON
	metadataJSON, err := json.Marshal(req.Metadata)
	if err != nil {
		as.logger.Error("Failed to marshal audit metadata",
			zap.Error(err),
			zap.String("action", req.Action),
		)
		return fmt.Errorf("failed to marshal audit metadata: %w", err)
	}

	// Insérer le log d'audit
	query := `
		INSERT INTO audit_logs (id, user_id, action, resource, resource_id, ip_address, user_agent, metadata, timestamp)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
	`

	_, err = as.db.ExecContext(ctx, query,
		uuid.New(),
		req.UserID,
		req.Action,
		req.Resource,
		req.ResourceID,
		req.IPAddress,
		req.UserAgent,
		metadataJSON,
		time.Now(),
	)

	if err != nil {
		as.logger.Error("Failed to log audit action",
			zap.Error(err),
			zap.String("action", req.Action),
			zap.String("resource", req.Resource),
		)
		return fmt.Errorf("failed to log audit action: %w", err)
	}

	as.logger.Debug("Audit action logged",
		zap.String("action", req.Action),
		zap.String("resource", req.Resource),
		zap.String("user_id", req.UserID.String()),
	)

	return nil
}

// LogLogin enregistre une tentative de connexion
func (as *AuditService) LogLogin(ctx context.Context, userID *uuid.UUID, success bool, ipAddress, userAgent string, metadata map[string]interface{}) error {
	action := "login_failed"
	if success {
		action = "login_success"
	}

	req := &AuditLogCreateRequest{
		UserID:    userID,
		Action:    action,
		Resource:  "user",
		IPAddress: ipAddress,
		UserAgent: userAgent,
		Metadata:  metadata,
	}

	return as.LogAction(ctx, req)
}

// LogLogout enregistre une déconnexion
func (as *AuditService) LogLogout(ctx context.Context, userID uuid.UUID, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:    &userID,
		Action:    "logout",
		Resource:  "user",
		IPAddress: ipAddress,
		UserAgent: userAgent,
		Metadata:  map[string]interface{}{},
	}

	return as.LogAction(ctx, req)
}

// LogUpload enregistre un upload de fichier
func (as *AuditService) LogUpload(ctx context.Context, userID uuid.UUID, resourceID uuid.UUID, fileName string, fileSize int64, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:     &userID,
		Action:     "upload",
		Resource:   "track",
		ResourceID: &resourceID,
		IPAddress:  ipAddress,
		UserAgent:  userAgent,
		Metadata: map[string]interface{}{
			"file_name": fileName,
			"file_size": fileSize,
		},
	}

	return as.LogAction(ctx, req)
}

// LogPermissionChange enregistre un changement de permission
func (as *AuditService) LogPermissionChange(ctx context.Context, userID uuid.UUID, targetUserID uuid.UUID, oldPermissions, newPermissions []string, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:     &userID,
		Action:     "permission_change",
		Resource:   "user",
		ResourceID: &targetUserID,
		IPAddress:  ipAddress,
		UserAgent:  userAgent,
		Metadata: map[string]interface{}{
			"old_permissions": oldPermissions,
			"new_permissions": newPermissions,
		},
	}

	return as.LogAction(ctx, req)
}

// LogDeletion enregistre une suppression
func (as *AuditService) LogDeletion(ctx context.Context, userID uuid.UUID, resource string, resourceID uuid.UUID, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:     &userID,
		Action:     "delete",
		Resource:   resource,
		ResourceID: &resourceID,
		IPAddress:  ipAddress,
		UserAgent:  userAgent,
		Metadata:   map[string]interface{}{},
	}

	return as.LogAction(ctx, req)
}

// BE-SEC-013: LogPasswordChange enregistre un changement de mot de passe
func (as *AuditService) LogPasswordChange(ctx context.Context, userID uuid.UUID, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:    &userID,
		Action:    "password_change",
		Resource:  "user",
		IPAddress: ipAddress,
		UserAgent: userAgent,
		Metadata:  map[string]interface{}{},
	}

	return as.LogAction(ctx, req)
}

// BE-SEC-013: LogPasswordResetRequest enregistre une demande de réinitialisation de mot de passe
func (as *AuditService) LogPasswordResetRequest(ctx context.Context, userID *uuid.UUID, email string, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:    userID,
		Action:    "password_reset_request",
		Resource:  "user",
		IPAddress: ipAddress,
		UserAgent: userAgent,
		Metadata: map[string]interface{}{
			"email": email,
		},
	}

	return as.LogAction(ctx, req)
}

// BE-SEC-013: LogPasswordReset enregistre une réinitialisation de mot de passe
func (as *AuditService) LogPasswordReset(ctx context.Context, userID uuid.UUID, success bool, ipAddress, userAgent string) error {
	action := "password_reset_failed"
	if success {
		action = "password_reset_success"
	}

	req := &AuditLogCreateRequest{
		UserID:    &userID,
		Action:    action,
		Resource:  "user",
		IPAddress: ipAddress,
		UserAgent: userAgent,
		Metadata:  map[string]interface{}{},
	}

	return as.LogAction(ctx, req)
}

// BE-SEC-013: LogTwoFactorEnabled enregistre l'activation de 2FA
func (as *AuditService) LogTwoFactorEnabled(ctx context.Context, userID uuid.UUID, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:    &userID,
		Action:    "2fa_enabled",
		Resource:  "user",
		IPAddress: ipAddress,
		UserAgent: userAgent,
		Metadata:  map[string]interface{}{},
	}

	return as.LogAction(ctx, req)
}

// BE-SEC-013: LogTwoFactorDisabled enregistre la désactivation de 2FA
func (as *AuditService) LogTwoFactorDisabled(ctx context.Context, userID uuid.UUID, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:    &userID,
		Action:    "2fa_disabled",
		Resource:  "user",
		IPAddress: ipAddress,
		UserAgent: userAgent,
		Metadata:  map[string]interface{}{},
	}

	return as.LogAction(ctx, req)
}

// BE-SEC-013: LogTwoFactorVerification enregistre une vérification 2FA
func (as *AuditService) LogTwoFactorVerification(ctx context.Context, userID uuid.UUID, success bool, ipAddress, userAgent string) error {
	action := "2fa_verification_failed"
	if success {
		action = "2fa_verification_success"
	}

	req := &AuditLogCreateRequest{
		UserID:    &userID,
		Action:    action,
		Resource:  "user",
		IPAddress: ipAddress,
		UserAgent: userAgent,
		Metadata:  map[string]interface{}{},
	}

	return as.LogAction(ctx, req)
}

// BE-SEC-013: LogAccessDenied enregistre un accès refusé
func (as *AuditService) LogAccessDenied(ctx context.Context, userID *uuid.UUID, resource string, resourceID *uuid.UUID, reason string, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:     userID,
		Action:     "access_denied",
		Resource:   resource,
		ResourceID: resourceID,
		IPAddress:  ipAddress,
		UserAgent:  userAgent,
		Metadata: map[string]interface{}{
			"reason": reason,
		},
	}

	return as.LogAction(ctx, req)
}

// BE-SEC-013: LogRoleChange enregistre un changement de rôle
func (as *AuditService) LogRoleChange(ctx context.Context, userID uuid.UUID, targetUserID uuid.UUID, oldRole, newRole string, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:     &userID,
		Action:     "role_change",
		Resource:   "user",
		ResourceID: &targetUserID,
		IPAddress:  ipAddress,
		UserAgent:  userAgent,
		Metadata: map[string]interface{}{
			"old_role": oldRole,
			"new_role": newRole,
		},
	}

	return as.LogAction(ctx, req)
}

// BE-SEC-013: LogAccountLocked enregistre un verrouillage de compte
func (as *AuditService) LogAccountLocked(ctx context.Context, email string, reason string, lockedUntil *time.Time, ipAddress, userAgent string) error {
	metadata := map[string]interface{}{
		"email":  email,
		"reason": reason,
	}
	if lockedUntil != nil {
		metadata["locked_until"] = lockedUntil.Format(time.RFC3339)
	}

	req := &AuditLogCreateRequest{
		UserID:    nil, // May not have userID if account locked before login
		Action:    "account_locked",
		Resource:  "user",
		IPAddress: ipAddress,
		UserAgent: userAgent,
		Metadata:  metadata,
	}

	return as.LogAction(ctx, req)
}

// BE-SEC-013: LogSecurityEvent enregistre un événement de sécurité générique
func (as *AuditService) LogSecurityEvent(ctx context.Context, userID *uuid.UUID, action string, resource string, resourceID *uuid.UUID, details map[string]interface{}, ipAddress, userAgent string) error {
	req := &AuditLogCreateRequest{
		UserID:     userID,
		Action:     action,
		Resource:   resource,
		ResourceID: resourceID,
		IPAddress:  ipAddress,
		UserAgent:  userAgent,
		Metadata:   details,
	}

	return as.LogAction(ctx, req)
}

// SearchLogs recherche des logs d'audit
func (as *AuditService) SearchLogs(ctx context.Context, req *AuditLogSearchRequest) ([]*AuditLog, error) {
	// Construire la requête dynamiquement
	query := `
		SELECT id, user_id, action, resource, resource_id, ip_address, user_agent, metadata, timestamp
		FROM audit_logs
		WHERE 1=1
	`
	args := []interface{}{}
	argIndex := 1

	if req.UserID != nil {
		query += fmt.Sprintf(" AND user_id = $%d", argIndex)
		args = append(args, *req.UserID)
		argIndex++
	}

	if req.Action != "" {
		query += fmt.Sprintf(" AND action = $%d", argIndex)
		args = append(args, req.Action)
		argIndex++
	}

	if req.Resource != "" {
		query += fmt.Sprintf(" AND resource = $%d", argIndex)
		args = append(args, req.Resource)
		argIndex++
	}

	if req.StartDate != nil {
		query += fmt.Sprintf(" AND timestamp >= $%d", argIndex)
		args = append(args, *req.StartDate)
		argIndex++
	}

	if req.EndDate != nil {
		query += fmt.Sprintf(" AND timestamp <= $%d", argIndex)
		args = append(args, *req.EndDate)
		argIndex++
	}

	query += " ORDER BY timestamp DESC"

	if req.Limit > 0 {
		query += fmt.Sprintf(" LIMIT $%d", argIndex)
		args = append(args, req.Limit)
		argIndex++
	}

	if req.Offset > 0 {
		query += fmt.Sprintf(" OFFSET $%d", argIndex)
		args = append(args, req.Offset)
	}

	rows, err := as.db.QueryContext(ctx, query, args...)
	if err != nil {
		as.logger.Error("Failed to search audit logs",
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to search audit logs: %w", err)
	}
	defer rows.Close()

	var logs []*AuditLog
	for rows.Next() {
		var log AuditLog
		err := rows.Scan(
			&log.ID,
			&log.UserID,
			&log.Action,
			&log.Resource,
			&log.ResourceID,
			&log.IPAddress,
			&log.UserAgent,
			&log.Metadata,
			&log.Timestamp,
		)
		if err != nil {
			as.logger.Error("Failed to scan audit log",
				zap.Error(err),
			)
			continue
		}
		logs = append(logs, &log)
	}

	return logs, nil
}

// CountLogs compte le nombre total de logs correspondant aux critères de recherche
// BE-API-034: Added total count for pagination
func (as *AuditService) CountLogs(ctx context.Context, req *AuditLogSearchRequest) (int64, error) {
	query := `
		SELECT COUNT(*)
		FROM audit_logs
		WHERE 1=1
	`
	args := []interface{}{}
	argIndex := 1

	if req.UserID != nil {
		query += fmt.Sprintf(" AND user_id = $%d", argIndex)
		args = append(args, *req.UserID)
		argIndex++
	}

	if req.Action != "" {
		query += fmt.Sprintf(" AND action = $%d", argIndex)
		args = append(args, req.Action)
		argIndex++
	}

	if req.Resource != "" {
		query += fmt.Sprintf(" AND resource = $%d", argIndex)
		args = append(args, req.Resource)
		argIndex++
	}

	if req.ResourceID != nil {
		query += fmt.Sprintf(" AND resource_id = $%d", argIndex)
		args = append(args, *req.ResourceID)
		argIndex++
	}

	if req.IPAddress != "" {
		query += fmt.Sprintf(" AND ip_address = $%d", argIndex)
		args = append(args, req.IPAddress)
		argIndex++
	}

	if req.UserAgent != "" {
		query += fmt.Sprintf(" AND user_agent LIKE $%d", argIndex)
		args = append(args, "%"+req.UserAgent+"%")
		argIndex++
	}

	if req.StartDate != nil {
		query += fmt.Sprintf(" AND timestamp >= $%d", argIndex)
		args = append(args, *req.StartDate)
		argIndex++
	}

	if req.EndDate != nil {
		query += fmt.Sprintf(" AND timestamp <= $%d", argIndex)
		args = append(args, *req.EndDate)
		argIndex++
	}

	var count int64
	err := as.db.QueryRowContext(ctx, query, args...).Scan(&count)
	if err != nil {
		as.logger.Error("Failed to count audit logs",
			zap.Error(err),
		)
		return 0, fmt.Errorf("failed to count audit logs: %w", err)
	}

	return count, nil
}

// GetStats récupère les statistiques d'audit
func (as *AuditService) GetStats(ctx context.Context, startDate, endDate time.Time) ([]*AuditStats, error) {
	query := `
		SELECT action, resource, COUNT(*) as action_count, 
		       COUNT(DISTINCT user_id) as unique_users,
		       COUNT(DISTINCT ip_address) as unique_ips
		FROM audit_logs
		WHERE timestamp BETWEEN $1 AND $2
		GROUP BY action, resource
		ORDER BY action_count DESC
	`

	rows, err := as.db.QueryContext(ctx, query, startDate, endDate)
	if err != nil {
		as.logger.Error("Failed to get audit stats",
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to get audit stats: %w", err)
	}
	defer rows.Close()

	var stats []*AuditStats
	for rows.Next() {
		var stat AuditStats
		err := rows.Scan(
			&stat.Action,
			&stat.Resource,
			&stat.ActionCount,
			&stat.UniqueUsers,
			&stat.UniqueIPs,
		)
		if err != nil {
			as.logger.Error("Failed to scan audit stat",
				zap.Error(err),
			)
			continue
		}
		stats = append(stats, &stat)
	}

	return stats, nil
}

// DetectSuspiciousActivity détecte les activités suspectes
func (as *AuditService) DetectSuspiciousActivity(ctx context.Context, hours int) ([]*SuspiciousActivity, error) {
	query := `
		WITH user_activity AS (
			SELECT 
				user_id,
				ip_address,
				COUNT(*) as action_count,
				COUNT(DISTINCT action) as unique_actions
			FROM audit_logs
			WHERE timestamp >= NOW() - INTERVAL '%d hours'
			GROUP BY user_id, ip_address
		)
		SELECT 
			user_id,
			ip_address,
			action_count,
			unique_actions,
			CASE 
				WHEN action_count > 1000 THEN 100
				WHEN action_count > 500 THEN 80
				WHEN action_count > 100 THEN 60
				WHEN action_count > 50 THEN 40
				WHEN action_count > 20 THEN 20
				ELSE 0
			END as risk_score
		FROM user_activity
		WHERE action_count > 20
		ORDER BY risk_score DESC, action_count DESC
	`

	rows, err := as.db.QueryContext(ctx, fmt.Sprintf(query, hours))
	if err != nil {
		as.logger.Error("Failed to detect suspicious activity",
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to detect suspicious activity: %w", err)
	}
	defer rows.Close()

	var activities []*SuspiciousActivity
	for rows.Next() {
		var activity SuspiciousActivity
		err := rows.Scan(
			&activity.UserID,
			&activity.IPAddress,
			&activity.ActionCount,
			&activity.UniqueActions,
			&activity.RiskScore,
		)
		if err != nil {
			as.logger.Error("Failed to scan suspicious activity",
				zap.Error(err),
			)
			continue
		}
		activities = append(activities, &activity)
	}

	return activities, nil
}

// CleanupOldLogs nettoie les anciens logs d'audit
func (as *AuditService) CleanupOldLogs(ctx context.Context, retentionDays int) (int64, error) {
	query := `
		DELETE FROM audit_logs 
		WHERE timestamp < NOW() - INTERVAL '%d days'
	`

	result, err := as.db.ExecContext(ctx, fmt.Sprintf(query, retentionDays))
	if err != nil {
		as.logger.Error("Failed to cleanup old audit logs",
			zap.Error(err),
		)
		return 0, fmt.Errorf("failed to cleanup old audit logs: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return 0, fmt.Errorf("failed to get rows affected: %w", err)
	}

	as.logger.Info("Old audit logs cleaned up",
		zap.Int64("deleted_count", rowsAffected),
		zap.Int("retention_days", retentionDays),
	)

	return rowsAffected, nil
}

// GetUserActivity récupère l'activité d'un utilisateur
func (as *AuditService) GetUserActivity(ctx context.Context, userID uuid.UUID, limit int) ([]*AuditLog, error) {
	req := &AuditLogSearchRequest{
		UserID: &userID,
		Limit:  limit,
	}

	return as.SearchLogs(ctx, req)
}

// GetIPActivity récupère l'activité d'une IP
func (as *AuditService) GetIPActivity(ctx context.Context, ipAddress string, limit int) ([]*AuditLog, error) {
	query := `
		SELECT id, user_id, action, resource, resource_id, ip_address, user_agent, metadata, timestamp
		FROM audit_logs
		WHERE ip_address = $1
		ORDER BY timestamp DESC
		LIMIT $2
	`

	rows, err := as.db.QueryContext(ctx, query, ipAddress, limit)
	if err != nil {
		as.logger.Error("Failed to get IP activity",
			zap.Error(err),
			zap.String("ip_address", ipAddress),
		)
		return nil, fmt.Errorf("failed to get IP activity: %w", err)
	}
	defer rows.Close()

	var logs []*AuditLog
	for rows.Next() {
		var log AuditLog
		err := rows.Scan(
			&log.ID,
			&log.UserID,
			&log.Action,
			&log.Resource,
			&log.ResourceID,
			&log.IPAddress,
			&log.UserAgent,
			&log.Metadata,
			&log.Timestamp,
		)
		if err != nil {
			as.logger.Error("Failed to scan audit log",
				zap.Error(err),
			)
			continue
		}
		logs = append(logs, &log)
	}

	return logs, nil
}
