package service

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/robfig/cron/v3"
	"gorm.io/gorm"

	coremodel "security-center-backend/internal/model"
	alertmodel "security-center-backend/internal/modules/alerts/model"
	alertrepo "security-center-backend/internal/modules/alerts/repository"
	"security-center-backend/internal/pkg/lark"
	"security-center-backend/internal/pkg/logger"
)

const (
	defaultVulnerabilitySLADueSoonCardTemplate = `{"config":{"wide_screen_mode":true},"header":{"template":"orange","title":{"tag":"plain_text","content":"Vulnerability SLA due soon"}},"elements":[{"tag":"markdown","content":"**[{{ .entity.public_id }}] {{ .entity.title }}**\nSeverity: {{ .entity.severity_level }}\nApplication: {{ .entity.application }}\nOwner: {{ .entity.owner_label }}\nSLA deadline: {{ .entity.sla_deadline }}\n[View vulnerability]({{ .entity.vulnerability_url }})"}]}`
	defaultVulnerabilitySLAOverdueCardTemplate = `{"config":{"wide_screen_mode":true},"header":{"template":"red","title":{"tag":"plain_text","content":"Vulnerability SLA overdue"}},"elements":[{"tag":"markdown","content":"**[{{ .entity.public_id }}] {{ .entity.title }}**\nSeverity: {{ .entity.severity_level }}\nApplication: {{ .entity.application }}\nOwner: {{ .entity.owner_label }}\nSLA deadline: {{ .entity.sla_deadline }}\nAction: remediation is overdue\n[View vulnerability]({{ .entity.vulnerability_url }})"}]}`
)

type VulnerabilitySLAReminderRuntime struct {
	service  *Service
	repo     alertrepo.SLAReminderDeliveryRepository
	logger   *logger.Logger
	sendCard func(ctx context.Context, appID string, appSecret string, receiveIDType string, receiveID string, card map[string]interface{}) error

	mu            sync.RWMutex
	cronScheduler *cron.Cron
	entryID       cron.EntryID
	isRunning     bool
	lastTickAt    *time.Time
	lastError     string
	lastErrorAt   *time.Time
}

type reminderRecipient struct {
	EmployeeID uint64
	Label      string
}

func NewVulnerabilitySLAReminderRuntime(service *Service, repo alertrepo.SLAReminderDeliveryRepository, log *logger.Logger) *VulnerabilitySLAReminderRuntime {
	return &VulnerabilitySLAReminderRuntime{
		service: service,
		repo:    repo,
		logger:  log,
		sendCard: func(ctx context.Context, appID string, appSecret string, receiveIDType string, receiveID string, card map[string]interface{}) error {
			return lark.NewClient(appID, appSecret).SendInteractiveCard(ctx, receiveIDType, receiveID, card)
		},
	}
}

func (rt *VulnerabilitySLAReminderRuntime) Start() (RuntimeStatus, error) {
	rt.mu.Lock()
	defer rt.mu.Unlock()

	if rt.isRunning {
		return rt.statusLocked(), nil
	}
	if rt.service == nil || rt.repo == nil {
		return rt.statusLocked(), fmt.Errorf("vulnerability SLA reminder runtime not configured")
	}

	rt.cronScheduler = cron.New()
	entryID, err := rt.cronScheduler.AddFunc("@every 1h", func() {
		if err := rt.RunOnce(context.Background(), time.Now().UTC()); err != nil {
			rt.noteError(err)
		}
	})
	if err != nil {
		rt.lastError = err.Error()
		now := time.Now().UTC()
		rt.lastErrorAt = &now
		return rt.statusLocked(), err
	}
	rt.entryID = entryID
	rt.cronScheduler.Start()
	rt.isRunning = true
	if rt.logger != nil {
		rt.logger.Info("vulnerability SLA reminder runtime started")
	}
	return rt.statusLocked(), nil
}

func (rt *VulnerabilitySLAReminderRuntime) Stop() {
	rt.mu.Lock()
	defer rt.mu.Unlock()
	rt.stopLocked("")
}

func (rt *VulnerabilitySLAReminderRuntime) StopWithDiagnostic(reason string) {
	rt.mu.Lock()
	defer rt.mu.Unlock()
	rt.stopLocked(reason)
}

func (rt *VulnerabilitySLAReminderRuntime) Refresh() (RuntimeStatus, error) {
	rt.Stop()
	return rt.Start()
}

func (rt *VulnerabilitySLAReminderRuntime) Status() RuntimeStatus {
	rt.mu.RLock()
	defer rt.mu.RUnlock()
	return rt.statusLocked()
}

func (rt *VulnerabilitySLAReminderRuntime) RunOnce(ctx context.Context, now time.Time) error {
	if rt == nil || rt.service == nil || rt.repo == nil {
		return fmt.Errorf("vulnerability SLA reminder runtime not configured")
	}
	if now.IsZero() {
		now = time.Now().UTC()
	}
	now = now.UTC()
	rt.mu.Lock()
	rt.lastTickAt = &now
	rt.mu.Unlock()

	settings, err := rt.service.getRawSettings(ctx)
	if err != nil {
		return err
	}
	if !slaReminderCanSend(settings.Notifications) {
		return nil
	}

	if err := rt.repo.RefreshOverdueFlags(ctx, now); err != nil {
		return err
	}

	dueSoonHours := settings.Notifications.VulnerabilitySLADueSoonBeforeHours
	if dueSoonHours <= 0 {
		dueSoonHours = 24
	}
	overdueRepeatHours := settings.Notifications.VulnerabilitySLAOverdueRepeatHours
	if overdueRepeatHours <= 0 {
		overdueRepeatHours = 24
	}

	windowEnd := now.Add(time.Duration(dueSoonHours) * time.Hour)
	dueSoon, err := rt.repo.ListDueSoonCandidates(ctx, now, windowEnd, 500)
	if err != nil {
		return err
	}
	if err := rt.processCandidates(ctx, settings, dueSoon, alertmodel.SLAReminderKindDueSoon, 0, now); err != nil {
		return err
	}

	overdue, err := rt.repo.ListOverdueCandidates(ctx, now, 500)
	if err != nil {
		return err
	}
	return rt.processCandidates(ctx, settings, overdue, alertmodel.SLAReminderKindOverdue, time.Duration(overdueRepeatHours)*time.Hour, now)
}

func (rt *VulnerabilitySLAReminderRuntime) processCandidates(ctx context.Context, settings *SettingsSnapshot, candidates []alertrepo.VulnerabilityReminderCandidate, kind alertmodel.SLAReminderKind, cooldown time.Duration, now time.Time) error {
	for i := range candidates {
		vuln := candidates[i].Vulnerability
		recipients, recipientErrs := rt.resolveReminderRecipients(ctx, vuln.OwnerEmployeeID, vuln.SubmitterUserID)
		for _, recipientErr := range recipientErrs {
			rt.recordReminderDelivery(ctx, vuln.ID, kind, 0, recipientErr.label, vuln.SLADeadline, alertmodel.DeliveryStatusError, recipientErr.err.Error(), nil, now)
		}
		if len(recipients) == 0 {
			continue
		}
		cardTemplate := defaultVulnerabilitySLADueSoonCardTemplate
		if kind == alertmodel.SLAReminderKindOverdue {
			cardTemplate = defaultVulnerabilitySLAOverdueCardTemplate
		}
		card, err := rt.service.renderCardTemplate(ctx, "vulnerability", vuln, cardTemplate)
		if err != nil {
			return err
		}
		for _, recipient := range recipients {
			shouldSend, err := rt.shouldSend(ctx, vuln.ID, kind, recipient.EmployeeID, vuln.SLADeadline, cooldown, now)
			if err != nil {
				return err
			}
			if !shouldSend {
				continue
			}
			rt.sendReminderToEmployee(ctx, settings, vuln.ID, kind, recipient, vuln.SLADeadline, card, now)
		}
	}
	return nil
}

type reminderRecipientError struct {
	label string
	err   error
}

func (rt *VulnerabilitySLAReminderRuntime) resolveReminderRecipients(ctx context.Context, ownerEmployeeID *uint64, submitterUserID uint64) ([]reminderRecipient, []reminderRecipientError) {
	seen := make(map[uint64]reminderRecipient, 2)
	if ownerEmployeeID != nil && *ownerEmployeeID > 0 {
		seen[*ownerEmployeeID] = reminderRecipient{EmployeeID: *ownerEmployeeID, Label: string(alertmodel.RecipientTargetEntityOwner)}
	}

	errs := make([]reminderRecipientError, 0, 1)
	if submitterUserID == 0 {
		errs = append(errs, reminderRecipientError{label: string(alertmodel.RecipientTargetEntitySubmitter), err: errors.New("entity has no submitter_user_id")})
		return mapReminderRecipients(seen), errs
	}
	if rt.service.submitterLookup == nil {
		errs = append(errs, reminderRecipientError{label: string(alertmodel.RecipientTargetEntitySubmitter), err: errors.New("submitter lookup not configured")})
		return mapReminderRecipients(seen), errs
	}
	submitterEmployeeID, err := rt.service.submitterLookup.FindEmployeeIDByUserID(ctx, submitterUserID)
	if err != nil {
		errs = append(errs, reminderRecipientError{label: string(alertmodel.RecipientTargetEntitySubmitter), err: fmt.Errorf("resolve submitter employee: %w", err)})
		return mapReminderRecipients(seen), errs
	}
	if submitterEmployeeID == 0 {
		errs = append(errs, reminderRecipientError{label: string(alertmodel.RecipientTargetEntitySubmitter), err: fmt.Errorf("user %d has no employee_id", submitterUserID)})
		return mapReminderRecipients(seen), errs
	}
	if existing, ok := seen[submitterEmployeeID]; ok {
		if !strings.Contains(existing.Label, string(alertmodel.RecipientTargetEntitySubmitter)) {
			existing.Label += "," + string(alertmodel.RecipientTargetEntitySubmitter)
			seen[submitterEmployeeID] = existing
		}
	} else {
		seen[submitterEmployeeID] = reminderRecipient{EmployeeID: submitterEmployeeID, Label: string(alertmodel.RecipientTargetEntitySubmitter)}
	}
	return mapReminderRecipients(seen), errs
}

func (rt *VulnerabilitySLAReminderRuntime) shouldSend(ctx context.Context, vulnerabilityID uint64, kind alertmodel.SLAReminderKind, employeeID uint64, slaDeadline time.Time, cooldown time.Duration, now time.Time) (bool, error) {
	last, err := rt.repo.LastSuccessfulDelivery(ctx, vulnerabilityID, kind, employeeID, slaDeadline)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return true, nil
		}
		return false, err
	}
	if kind == alertmodel.SLAReminderKindDueSoon {
		return false, nil
	}
	if cooldown <= 0 {
		return true, nil
	}
	return !last.SentAt.Add(cooldown).After(now), nil
}

func (rt *VulnerabilitySLAReminderRuntime) sendReminderToEmployee(ctx context.Context, settings *SettingsSnapshot, vulnerabilityID uint64, kind alertmodel.SLAReminderKind, recipient reminderRecipient, slaDeadline time.Time, card map[string]interface{}, sentAt time.Time) bool {
	if rt.service.employeeLookup == nil {
		rt.recordReminderDelivery(ctx, vulnerabilityID, kind, recipient.EmployeeID, recipient.Label, slaDeadline, alertmodel.DeliveryStatusError, "employee lookup not configured", card, sentAt)
		return false
	}
	emp, err := rt.service.employeeLookup.FindByID(ctx, recipient.EmployeeID)
	if err != nil {
		rt.recordReminderDelivery(ctx, vulnerabilityID, kind, recipient.EmployeeID, recipient.Label, slaDeadline, alertmodel.DeliveryStatusError, err.Error(), card, sentAt)
		return false
	}
	appID := strings.TrimSpace(settings.Notifications.LarkAppID)
	appSecret := strings.TrimSpace(settings.Notifications.LarkAppSecret)
	if appID == "" || appSecret == "" {
		rt.recordReminderDelivery(ctx, vulnerabilityID, kind, recipient.EmployeeID, recipient.Label, slaDeadline, alertmodel.DeliveryStatusError, "lark app credentials not configured", card, sentAt)
		return false
	}
	receiveIDType := normalizeReceiveIDType(settings.Notifications.LarkReceiveIDType)
	receiveID, err := employeeReceiveID(emp, receiveIDType)
	if err != nil {
		rt.recordReminderDelivery(ctx, vulnerabilityID, kind, recipient.EmployeeID, recipient.Label, slaDeadline, alertmodel.DeliveryStatusError, err.Error(), card, sentAt)
		return false
	}
	if rt.sendCard == nil {
		rt.sendCard = func(ctx context.Context, appID string, appSecret string, receiveIDType string, receiveID string, card map[string]interface{}) error {
			return lark.NewClient(appID, appSecret).SendInteractiveCard(ctx, receiveIDType, receiveID, card)
		}
	}
	if err := rt.sendCard(ctx, appID, appSecret, receiveIDType, receiveID, card); err != nil {
		rt.recordReminderDelivery(ctx, vulnerabilityID, kind, recipient.EmployeeID, recipient.Label, slaDeadline, alertmodel.DeliveryStatusError, err.Error(), card, sentAt)
		return false
	}
	rt.recordReminderDelivery(ctx, vulnerabilityID, kind, recipient.EmployeeID, recipient.Label, slaDeadline, alertmodel.DeliveryStatusSuccess, "", card, sentAt)
	return true
}

func (rt *VulnerabilitySLAReminderRuntime) recordReminderDelivery(ctx context.Context, vulnerabilityID uint64, kind alertmodel.SLAReminderKind, employeeID uint64, label string, slaDeadline time.Time, status alertmodel.DeliveryStatus, errMsg string, card map[string]interface{}, sentAt time.Time) {
	if rt.repo == nil {
		return
	}
	if sentAt.IsZero() {
		sentAt = time.Now().UTC()
	}
	var cardPreview coremodel.JSONB
	if card != nil {
		cardPreview = coremodel.JSONB(card)
	}
	delivery := &alertmodel.VulnerabilitySLAReminderDelivery{
		VulnerabilityID:     vulnerabilityID,
		ReminderKind:        kind,
		RecipientEmployeeID: employeeID,
		RecipientLabel:      trimRecipientLabel(label),
		SLADeadlineSnapshot: slaDeadline,
		Status:              status,
		SentAt:              sentAt.UTC(),
		ErrorMessage:        errMsg,
		CardPreview:         cardPreview,
	}
	if err := rt.repo.Create(ctx, delivery); err != nil && rt.logger != nil {
		rt.logger.Error("vulnerability SLA reminder delivery persistence failed", "vulnerability_id", vulnerabilityID, "kind", kind, "employee_id", employeeID, "error", err)
	}
}

func (rt *VulnerabilitySLAReminderRuntime) noteError(err error) {
	if err == nil {
		return
	}
	now := time.Now().UTC()
	rt.mu.Lock()
	rt.lastError = err.Error()
	rt.lastErrorAt = &now
	rt.mu.Unlock()
	if rt.logger != nil {
		rt.logger.Warn("vulnerability SLA reminder runtime error", "error", err)
	}
}

func (rt *VulnerabilitySLAReminderRuntime) stopLocked(reason string) {
	if rt.cronScheduler != nil {
		ctx := rt.cronScheduler.Stop()
		<-ctx.Done()
	}
	rt.cronScheduler = nil
	rt.entryID = 0
	rt.isRunning = false
	if reason != "" {
		rt.lastError = reason
		now := time.Now().UTC()
		rt.lastErrorAt = &now
	}
}

func (rt *VulnerabilitySLAReminderRuntime) statusLocked() RuntimeStatus {
	status := RuntimeStatus{SchedulerRunning: rt.isRunning}
	if rt.entryID != 0 {
		status.ScheduledRules = 1
	}
	if rt.lastTickAt != nil {
		status.LastTickAt = rt.lastTickAt.Format(time.RFC3339)
	}
	status.LastError = rt.lastError
	if rt.lastErrorAt != nil {
		status.LastErrorAt = rt.lastErrorAt.Format(time.RFC3339)
	}
	return status
}

func slaReminderCanSend(settings NotificationSettings) bool {
	return settings.LarkAlerts &&
		settings.VulnerabilitySLARemindersEnabled &&
		strings.TrimSpace(settings.LarkAppID) != "" &&
		strings.TrimSpace(settings.LarkAppSecret) != ""
}

func mapReminderRecipients(seen map[uint64]reminderRecipient) []reminderRecipient {
	out := make([]reminderRecipient, 0, len(seen))
	for _, recipient := range seen {
		out = append(out, recipient)
	}
	return out
}
