package service

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

	"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
	"go.uber.org/zap"
)

// WebhookService handles sending alert notifications via webhooks.
type WebhookService struct {
	opsService *OpsService
	httpClient *http.Client
}

// NewWebhookService creates a new webhook service.
func NewWebhookService(opsService *OpsService) *WebhookService {
	return &WebhookService{
		opsService: opsService,
		httpClient: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// SendAlertWebhook sends an alert notification to configured webhook URLs.
func (s *WebhookService) SendAlertWebhook(ctx context.Context, rule *OpsAlertRule, event *OpsAlertEvent, resolved bool) error {
	if s == nil || s.opsService == nil {
		return nil
	}

	cfg, err := s.opsService.GetWebhookNotificationConfig(ctx)
	if err != nil || cfg == nil || !cfg.Alert.Enabled {
		return nil
	}

	if len(cfg.Alert.URLs) == 0 {
		return nil
	}

	// Check severity threshold
	if !shouldSendWebhookByMinSeverity(cfg.Alert.MinSeverity, rule.Severity) {
		return nil
	}

	// Check if resolved alerts should be sent
	if resolved && !cfg.Alert.IncludeResolved {
		return nil
	}

	payload := s.buildAlertPayload(rule, event, resolved)
	if err := s.signPayload(payload, cfg.Alert.Secret); err != nil {
		logger.L().Warn("failed to sign webhook payload", zap.Error(err))
	}

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

	var lastErr error
	for _, url := range cfg.Alert.URLs {
		url = strings.TrimSpace(url)
		if url == "" {
			continue
		}

		if err := s.sendWebhook(ctx, url, payloadBytes, cfg.Alert.TimeoutSeconds, cfg.Alert.Secret); err != nil {
			logger.L().Warn("failed to send webhook", zap.String("url", url), zap.Error(err))
			lastErr = err
			continue
		}
	}

	return lastErr
}

// buildAlertPayload constructs the webhook payload for an alert.
func (s *WebhookService) buildAlertPayload(rule *OpsAlertRule, event *OpsAlertEvent, resolved bool) *OpsWebhookPayload {
	payload := &OpsWebhookPayload{
		Type:      "alert",
		Timestamp: time.Now().UTC().Format(time.RFC3339),
		Data: OpsWebhookData{
			Rule:  rule,
			Event: event,
		},
	}

	if resolved {
		payload.Type = "alert_resolved"
		payload.Data.ResolvedAt = time.Now().UTC().Format(time.RFC3339)
	}

	return payload
}

// signPayload adds HMAC signature to the payload if a secret is configured.
func (s *WebhookService) signPayload(payload *OpsWebhookPayload, secret string) error {
	if secret == "" || payload == nil {
		return nil
	}

	// Create a copy of payload without signature for signing
	signPayload := &OpsWebhookPayload{
		Type:      payload.Type,
		Timestamp: payload.Timestamp,
		Data:      payload.Data,
	}

	data, err := json.Marshal(signPayload)
	if err != nil {
		return err
	}

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write(data)
	payload.Signature = hex.EncodeToString(mac.Sum(nil))

	return nil
}

// sendWebhook sends the webhook payload to a single URL.
func (s *WebhookService) sendWebhook(ctx context.Context, url string, payload []byte, timeoutSeconds int, secret string) error {
	if s.httpClient == nil {
		return errors.New("http client not initialized")
	}

	timeout := time.Duration(timeoutSeconds) * time.Second
	if timeout <= 0 {
		timeout = 10 * time.Second
	}

	client := &http.Client{Timeout: timeout}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", "Sub2API-Webhook/1.0")
	if secret != "" {
		req.Header.Set("X-Webhook-Signature", "sha256="+hex.EncodeToString(hmac.New(sha256.New, []byte(secret)).Sum(payload)))
	}

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("webhook returned status %d: %s", resp.StatusCode, string(body))
	}

	return nil
}

// shouldSendWebhookByMinSeverity checks if the alert severity meets the minimum threshold.
func shouldSendWebhookByMinSeverity(minSeverity, ruleSeverity string) bool {
	minSeverity = strings.TrimSpace(strings.ToLower(minSeverity))
	ruleSeverity = strings.TrimSpace(strings.ToLower(ruleSeverity))

	severityLevels := map[string]int{
		"critical": 3,
		"warning":  2,
		"info":     1,
	}

	minLevel, okMin := severityLevels[minSeverity]
	ruleLevel, okRule := severityLevels[ruleSeverity]

	if !okMin || !okRule {
		return true // If unknown severity, send by default
	}

	return ruleLevel >= minLevel
}
