package services

import (
	"bytes"
	"context"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"veza-backend-api/internal/models"

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

// WebhookService gère les webhooks
type WebhookService struct {
	db     *gorm.DB
	logger *zap.Logger
	secret string
	client *http.Client
}

// WebhookPayload représente le payload d'un webhook
type WebhookPayload struct {
	Event     string                 `json:"event"`
	Timestamp time.Time              `json:"timestamp"`
	Data      map[string]interface{} `json:"data"`
}

// NewWebhookService crée un nouveau service de webhooks
func NewWebhookService(db *gorm.DB, logger *zap.Logger, secret string) *WebhookService {
	return &WebhookService{
		db:     db,
		logger: logger,
		secret: secret,
		client: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
}

// GenerateAPIKey génère une clé API sécurisée pour un webhook (BE-SEC-012)
func (s *WebhookService) GenerateAPIKey() (string, error) {
	// Générer 32 bytes aléatoires
	bytes := make([]byte, 32)
	if _, err := rand.Read(bytes); err != nil {
		return "", fmt.Errorf("failed to generate random bytes: %w", err)
	}

	// Encoder en base64 URL-safe et préfixer avec "whk_" pour identifier les clés webhook
	apiKey := "whk_" + base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(bytes)
	return apiKey, nil
}

// RegisterWebhook enregistre une nouvelle URL de webhook avec génération de clé API (BE-SEC-012)
func (s *WebhookService) RegisterWebhook(ctx context.Context, userID uuid.UUID, url string, events []string) (*models.Webhook, error) {
	// Générer une clé API unique
	apiKey, err := s.GenerateAPIKey()
	if err != nil {
		return nil, fmt.Errorf("failed to generate API key: %w", err)
	}

	webhook := &models.Webhook{
		UserID:    userID,
		URL:       url,
		Events:    events,
		Active:    true,
		APIKey:    apiKey,
		CreatedAt: time.Now(),
	}

	if err := s.db.WithContext(ctx).Create(webhook).Error; err != nil {
		return nil, fmt.Errorf("failed to register webhook: %w", err)
	}

	s.logger.Info("Webhook registered",
		zap.String("user_id", userID.String()),
		zap.String("url", url),
		zap.Strings("events", events),
		zap.String("api_key_prefix", apiKey[:8]+"..."))

	return webhook, nil
}

// DeliverWebhook envoie un webhook avec retry et signature HMAC
func (s *WebhookService) DeliverWebhook(ctx context.Context, webhook *models.Webhook, event string, data map[string]interface{}) error {
	payload := WebhookPayload{
		Event:     event,
		Timestamp: time.Now(),
		Data:      data,
	}

	jsonData, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("failed to marshal payload: %w", err)
	}

	// Générer signature HMAC
	signature := s.generateSignature(jsonData)

	// Créer la requête HTTP
	req, err := http.NewRequestWithContext(ctx, "POST", webhook.URL, bytes.NewBuffer(jsonData))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Veza-Signature", signature)
	req.Header.Set("X-Veza-Event", event)
	req.Header.Set("X-Veza-Timestamp", payload.Timestamp.Format(time.RFC3339))

	// FIX #11: Propager le request_id depuis le contexte si disponible
	if requestID := extractRequestIDFromContext(ctx); requestID != "" {
		req.Header.Set("X-Request-ID", requestID)
	}

	// Envoyer avec retry
	maxRetries := 3
	backoff := time.Second

	for i := 0; i < maxRetries; i++ {
		resp, err := s.client.Do(req)
		if err != nil {
			s.logger.Warn("Webhook delivery failed, retrying",
				zap.Int("attempt", i+1),
				zap.Error(err))

			if i < maxRetries-1 {
				time.Sleep(backoff)
				backoff *= 2 // Exponential backoff
				continue
			}

			return fmt.Errorf("webhook delivery failed after %d attempts: %w", maxRetries, err)
		}

		defer resp.Body.Close()

		if resp.StatusCode >= 200 && resp.StatusCode < 300 {
			s.logger.Info("Webhook delivered successfully",
				zap.String("url", webhook.URL),
				zap.String("event", event))
			return nil
		}

		s.logger.Warn("Webhook returned non-200 status",
			zap.String("url", webhook.URL),
			zap.Int("status", resp.StatusCode))
	}

	return fmt.Errorf("webhook delivery failed")
}

// generateSignature génère une signature HMAC-SHA256
func (s *WebhookService) generateSignature(payload []byte) string {
	mac := hmac.New(sha256.New, []byte(s.secret))
	mac.Write(payload)
	return hex.EncodeToString(mac.Sum(nil))
}

// VerifySignature vérifie une signature HMAC
func (s *WebhookService) VerifySignature(signature string, payload []byte) bool {
	expected := s.generateSignature(payload)
	return hmac.Equal([]byte(signature), []byte(expected))
}

// TriggerEvent déclenche un événement pour tous les webhooks concernés
func (s *WebhookService) TriggerEvent(ctx context.Context, event string, data map[string]interface{}, userID *uuid.UUID) error {
	// Récupérer les webhooks actifs pour cet événement
	var webhooks []models.Webhook
	query := s.db.WithContext(ctx).Where("active = ? AND events @> ARRAY[?]", true, event)

	if userID != nil {
		query = query.Where("user_id = ?", *userID)
	}

	if err := query.Find(&webhooks).Error; err != nil {
		return fmt.Errorf("failed to fetch webhooks: %w", err)
	}

	// Envoyer les webhooks en async
	for _, webhook := range webhooks {
		go func(w models.Webhook) {
			if err := s.DeliverWebhook(ctx, &w, event, data); err != nil {
				s.logger.Error("Failed to deliver webhook",
					zap.Error(err),
					zap.String("url", w.URL),
					zap.String("event", event))
			}
		}(webhook)
	}

	return nil
}

// ListWebhooks liste les webhooks d'un utilisateur
func (s *WebhookService) ListWebhooks(ctx context.Context, userID uuid.UUID) ([]models.Webhook, error) {
	var webhooks []models.Webhook

	if err := s.db.WithContext(ctx).
		Where("user_id = ?", userID).
		Find(&webhooks).Error; err != nil {
		return nil, fmt.Errorf("failed to list webhooks: %w", err)
	}

	return webhooks, nil
}

// GetWebhook récupère un webhook par son ID et userID
func (s *WebhookService) GetWebhook(ctx context.Context, webhookID, userID uuid.UUID) (*models.Webhook, error) {
	var webhook models.Webhook
	if err := s.db.WithContext(ctx).
		Where("id = ? AND user_id = ?", webhookID, userID).
		First(&webhook).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("webhook not found")
		}
		return nil, fmt.Errorf("failed to get webhook: %w", err)
	}
	return &webhook, nil
}

// DeleteWebhook supprime un webhook
func (s *WebhookService) DeleteWebhook(ctx context.Context, webhookID, userID uuid.UUID) error {
	result := s.db.WithContext(ctx).
		Where("id = ? AND user_id = ?", webhookID, userID).
		Delete(&models.Webhook{})

	if result.Error != nil {
		return fmt.Errorf("failed to delete webhook: %w", result.Error)
	}

	if result.RowsAffected == 0 {
		return fmt.Errorf("webhook not found")
	}

	return nil
}

// ValidateAPIKey valide une clé API et retourne le webhook associé (BE-SEC-012)
func (s *WebhookService) ValidateAPIKey(ctx context.Context, apiKey string) (*models.Webhook, error) {
	if !strings.HasPrefix(apiKey, "whk_") {
		return nil, fmt.Errorf("invalid API key format")
	}

	var webhook models.Webhook
	if err := s.db.WithContext(ctx).
		Where("api_key = ? AND active = ?", apiKey, true).
		First(&webhook).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("invalid API key")
		}
		return nil, fmt.Errorf("failed to validate API key: %w", err)
	}

	return &webhook, nil
}

// RegenerateAPIKey régénère la clé API d'un webhook (BE-SEC-012)
func (s *WebhookService) RegenerateAPIKey(ctx context.Context, webhookID, userID uuid.UUID) (string, error) {
	// Vérifier que le webhook existe et appartient à l'utilisateur
	webhook, err := s.GetWebhook(ctx, webhookID, userID)
	if err != nil {
		return "", err
	}

	// Générer une nouvelle clé API
	newAPIKey, err := s.GenerateAPIKey()
	if err != nil {
		return "", fmt.Errorf("failed to generate API key: %w", err)
	}

	// Mettre à jour le webhook
	webhook.APIKey = newAPIKey
	webhook.UpdatedAt = time.Now()

	if err := s.db.WithContext(ctx).Save(webhook).Error; err != nil {
		return "", fmt.Errorf("failed to update webhook: %w", err)
	}

	s.logger.Info("Webhook API key regenerated",
		zap.String("webhook_id", webhookID.String()),
		zap.String("user_id", userID.String()))

	return newAPIKey, nil
}
