package repository

import (
	"context"
	"errors"
	"strings"
	"time"

	"gorm.io/gorm"

	alertmodel "security-center-backend/internal/modules/alerts/model"
	vulnmodel "security-center-backend/internal/modules/vuln/model"
)

type RuleRepository interface {
	List(ctx context.Context) ([]alertmodel.LarkAlertRule, error)
	Get(ctx context.Context, id uint64) (*alertmodel.LarkAlertRule, error)
	Create(ctx context.Context, rule *alertmodel.LarkAlertRule) error
	Update(ctx context.Context, rule *alertmodel.LarkAlertRule) error
	Delete(ctx context.Context, id uint64) error
}

type RunRepository interface {
	Create(ctx context.Context, run *alertmodel.LarkAlertRun) error
	Update(ctx context.Context, run *alertmodel.LarkAlertRun) error
	ListRecent(ctx context.Context, limit int) ([]alertmodel.LarkAlertRun, error)
	Get(ctx context.Context, id uint64) (*alertmodel.LarkAlertRun, error)
}

type DeliveryRepository interface {
	Create(ctx context.Context, delivery *alertmodel.LarkAlertDelivery) error
	ListByRun(ctx context.Context, runID uint64, limit int) ([]alertmodel.LarkAlertDelivery, error)
}

type RuntimeOffsetRepository interface {
	Get(ctx context.Context, key string) (uint64, error)
	Advance(ctx context.Context, key string, lastProcessedID uint64) error
}

type VulnerabilityReminderCandidate struct {
	vulnmodel.Vulnerability
}

type SLAReminderDeliveryRepository interface {
	ListDueSoonCandidates(ctx context.Context, now time.Time, windowEnd time.Time, limit int) ([]VulnerabilityReminderCandidate, error)
	ListOverdueCandidates(ctx context.Context, now time.Time, limit int) ([]VulnerabilityReminderCandidate, error)
	RefreshOverdueFlags(ctx context.Context, now time.Time) error
	LastSuccessfulDelivery(ctx context.Context, vulnerabilityID uint64, kind alertmodel.SLAReminderKind, recipientEmployeeID uint64, slaDeadline time.Time) (*alertmodel.VulnerabilitySLAReminderDelivery, error)
	Create(ctx context.Context, delivery *alertmodel.VulnerabilitySLAReminderDelivery) error
}

type gormRuleRepository struct{ db *gorm.DB }
type gormRunRepository struct{ db *gorm.DB }
type gormDeliveryRepository struct{ db *gorm.DB }
type gormRuntimeOffsetRepository struct{ db *gorm.DB }
type gormSLAReminderDeliveryRepository struct{ db *gorm.DB }

func NewRuleRepository(db *gorm.DB) RuleRepository {
	return &gormRuleRepository{db: db}
}

func NewRunRepository(db *gorm.DB) RunRepository {
	return &gormRunRepository{db: db}
}

func NewDeliveryRepository(db *gorm.DB) DeliveryRepository {
	return &gormDeliveryRepository{db: db}
}

func NewSLAReminderDeliveryRepository(db *gorm.DB) SLAReminderDeliveryRepository {
	if db == nil {
		return nil
	}
	return &gormSLAReminderDeliveryRepository{db: db}
}

func NewRuntimeOffsetRepository(db *gorm.DB) RuntimeOffsetRepository {
	if db == nil {
		return nil
	}
	return &gormRuntimeOffsetRepository{db: db}
}

func (r *gormRuleRepository) List(ctx context.Context) ([]alertmodel.LarkAlertRule, error) {
	var rules []alertmodel.LarkAlertRule
	if err := r.db.WithContext(ctx).Order("id DESC").Find(&rules).Error; err != nil {
		return nil, err
	}
	return rules, nil
}

func (r *gormRuleRepository) Get(ctx context.Context, id uint64) (*alertmodel.LarkAlertRule, error) {
	var rule alertmodel.LarkAlertRule
	if err := r.db.WithContext(ctx).First(&rule, id).Error; err != nil {
		return nil, err
	}
	return &rule, nil
}

func (r *gormRuleRepository) Create(ctx context.Context, rule *alertmodel.LarkAlertRule) error {
	return r.db.WithContext(ctx).Create(rule).Error
}

func (r *gormRuleRepository) Update(ctx context.Context, rule *alertmodel.LarkAlertRule) error {
	return r.db.WithContext(ctx).Save(rule).Error
}

func (r *gormRuleRepository) Delete(ctx context.Context, id uint64) error {
	return r.db.WithContext(ctx).Delete(&alertmodel.LarkAlertRule{}, id).Error
}

func (r *gormRunRepository) Create(ctx context.Context, run *alertmodel.LarkAlertRun) error {
	return r.db.WithContext(ctx).Create(run).Error
}

func (r *gormRunRepository) Update(ctx context.Context, run *alertmodel.LarkAlertRun) error {
	return r.db.WithContext(ctx).Save(run).Error
}

func (r *gormRunRepository) ListRecent(ctx context.Context, limit int) ([]alertmodel.LarkAlertRun, error) {
	if limit <= 0 || limit > 200 {
		limit = 50
	}
	var runs []alertmodel.LarkAlertRun
	if err := r.db.WithContext(ctx).Order("id DESC").Limit(limit).Find(&runs).Error; err != nil {
		return nil, err
	}
	return runs, nil
}

func (r *gormRunRepository) Get(ctx context.Context, id uint64) (*alertmodel.LarkAlertRun, error) {
	var run alertmodel.LarkAlertRun
	if err := r.db.WithContext(ctx).First(&run, id).Error; err != nil {
		return nil, err
	}
	return &run, nil
}

func (r *gormDeliveryRepository) Create(ctx context.Context, delivery *alertmodel.LarkAlertDelivery) error {
	return r.db.WithContext(ctx).Create(delivery).Error
}

func (r *gormDeliveryRepository) ListByRun(ctx context.Context, runID uint64, limit int) ([]alertmodel.LarkAlertDelivery, error) {
	if limit <= 0 || limit > 500 {
		limit = 200
	}
	var deliveries []alertmodel.LarkAlertDelivery
	if err := r.db.WithContext(ctx).
		Where("run_id = ?", runID).
		Order("id DESC").
		Limit(limit).
		Find(&deliveries).Error; err != nil {
		return nil, err
	}
	return deliveries, nil
}

func (r *gormRuntimeOffsetRepository) Get(ctx context.Context, key string) (uint64, error) {
	key = strings.TrimSpace(key)
	if key == "" {
		return 0, errors.New("runtime offset key is required")
	}
	var offset alertmodel.AlertRuntimeOffset
	err := r.db.WithContext(ctx).
		Where("offset_key = ?", key).
		First(&offset).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return 0, nil
		}
		return 0, err
	}
	return offset.LastProcessedID, nil
}

func (r *gormRuntimeOffsetRepository) Advance(ctx context.Context, key string, lastProcessedID uint64) error {
	key = strings.TrimSpace(key)
	if key == "" {
		return errors.New("runtime offset key is required")
	}
	db := r.db.WithContext(ctx)
	result := db.Model(&alertmodel.AlertRuntimeOffset{}).
		Where("offset_key = ?", key).
		Where("last_processed_id < ?", lastProcessedID).
		Update("last_processed_id", lastProcessedID)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected > 0 {
		return nil
	}

	return db.Transaction(func(tx *gorm.DB) error {
		var offset alertmodel.AlertRuntimeOffset
		err := tx.Where("offset_key = ?", key).
			First(&offset).Error
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return tx.Create(&alertmodel.AlertRuntimeOffset{
					OffsetKey:       key,
					LastProcessedID: lastProcessedID,
				}).Error
			}
			return err
		}
		if offset.LastProcessedID >= lastProcessedID {
			return nil
		}
		return tx.Model(&offset).Update("last_processed_id", lastProcessedID).Error
	})
}

func (r *gormSLAReminderDeliveryRepository) ListDueSoonCandidates(ctx context.Context, now time.Time, windowEnd time.Time, limit int) ([]VulnerabilityReminderCandidate, error) {
	if limit <= 0 || limit > 500 {
		limit = 200
	}
	var vulns []vulnmodel.Vulnerability
	if err := r.db.WithContext(ctx).
		Where("status NOT IN ?", terminalVulnerabilityStatuses()).
		Where("sla_deadline > ? AND sla_deadline <= ?", now, windowEnd).
		Order("sla_deadline ASC").
		Limit(limit).
		Find(&vulns).Error; err != nil {
		return nil, err
	}
	return vulnerabilityReminderCandidates(vulns), nil
}

func (r *gormSLAReminderDeliveryRepository) ListOverdueCandidates(ctx context.Context, now time.Time, limit int) ([]VulnerabilityReminderCandidate, error) {
	if limit <= 0 || limit > 500 {
		limit = 200
	}
	var vulns []vulnmodel.Vulnerability
	if err := r.db.WithContext(ctx).
		Where("status NOT IN ?", terminalVulnerabilityStatuses()).
		Where("sla_deadline <= ?", now).
		Order("sla_deadline ASC").
		Limit(limit).
		Find(&vulns).Error; err != nil {
		return nil, err
	}
	return vulnerabilityReminderCandidates(vulns), nil
}

func (r *gormSLAReminderDeliveryRepository) RefreshOverdueFlags(ctx context.Context, now time.Time) error {
	if err := r.db.WithContext(ctx).
		Model(&vulnmodel.Vulnerability{}).
		Where("status NOT IN ?", terminalVulnerabilityStatuses()).
		Where("sla_deadline <= ?", now).
		Where("is_overdue = ?", false).
		Update("is_overdue", true).Error; err != nil {
		return err
	}
	return r.db.WithContext(ctx).
		Model(&vulnmodel.Vulnerability{}).
		Where("(status IN ? OR sla_deadline > ?)", terminalVulnerabilityStatuses(), now).
		Where("is_overdue = ?", true).
		Update("is_overdue", false).Error
}

func (r *gormSLAReminderDeliveryRepository) LastSuccessfulDelivery(ctx context.Context, vulnerabilityID uint64, kind alertmodel.SLAReminderKind, recipientEmployeeID uint64, slaDeadline time.Time) (*alertmodel.VulnerabilitySLAReminderDelivery, error) {
	var delivery alertmodel.VulnerabilitySLAReminderDelivery
	err := r.db.WithContext(ctx).
		Where("vulnerability_id = ?", vulnerabilityID).
		Where("reminder_kind = ?", kind).
		Where("recipient_employee_id = ?", recipientEmployeeID).
		Where("sla_deadline_snapshot = ?", slaDeadline).
		Where("status = ?", alertmodel.DeliveryStatusSuccess).
		Order("sent_at DESC").
		First(&delivery).Error
	if err != nil {
		return nil, err
	}
	return &delivery, nil
}

func (r *gormSLAReminderDeliveryRepository) Create(ctx context.Context, delivery *alertmodel.VulnerabilitySLAReminderDelivery) error {
	return r.db.WithContext(ctx).Create(delivery).Error
}

func terminalVulnerabilityStatuses() []vulnmodel.VulnerabilityStatus {
	return []vulnmodel.VulnerabilityStatus{
		vulnmodel.StatusFixed,
		vulnmodel.StatusFalsePositive,
		vulnmodel.StatusRiskAccepted,
	}
}

func vulnerabilityReminderCandidates(vulns []vulnmodel.Vulnerability) []VulnerabilityReminderCandidate {
	out := make([]VulnerabilityReminderCandidate, 0, len(vulns))
	for i := range vulns {
		out = append(out, VulnerabilityReminderCandidate{Vulnerability: vulns[i]})
	}
	return out
}
