package service

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

	"github.com/robfig/cron/v3"

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

type AlertRuntimeControl struct {
	service   *Service
	rulesRepo alertrepo.RuleRepository
	offsets   alertrepo.RuntimeOffsetRepository
	eventFeed EventFeed
	logger    *logger.Logger

	mu            sync.RWMutex
	cronScheduler *cron.Cron
	entryIDs      []cron.EntryID
	isRunning     bool

	lastTickAt  *time.Time
	lastError   string
	lastErrorAt *time.Time

	lastVulnEventID    uint64
	lastVulnCreateID   uint64
	lastAssetHistoryID uint64
}

const (
	alertOffsetVulnerabilityCreate = "vulnerability_create"
	alertOffsetVulnerabilityEvent  = "vulnerability_event"
	alertOffsetAssetHistory        = "asset_history"
)

func NewAlertRuntimeControl(service *Service, rulesRepo alertrepo.RuleRepository, db interface{}, log *logger.Logger) *AlertRuntimeControl {
	gormDB := asDB(db)
	return &AlertRuntimeControl{
		service:          service,
		rulesRepo:        rulesRepo,
		offsets:          alertrepo.NewRuntimeOffsetRepository(gormDB),
		eventFeed:        newGormEventFeed(gormDB),
		logger:           log,
		lastVulnCreateID: 0,
	}
}

func NewAlertRuntimeControlWithFeed(service *Service, rulesRepo alertrepo.RuleRepository, feed EventFeed, log *logger.Logger) *AlertRuntimeControl {
	return &AlertRuntimeControl{
		service:          service,
		rulesRepo:        rulesRepo,
		eventFeed:        feed,
		logger:           log,
		lastVulnCreateID: 0,
	}
}

func NewAlertRuntimeControlWithFeedAndOffsets(service *Service, rulesRepo alertrepo.RuleRepository, feed EventFeed, offsets alertrepo.RuntimeOffsetRepository, log *logger.Logger) *AlertRuntimeControl {
	return &AlertRuntimeControl{
		service:          service,
		rulesRepo:        rulesRepo,
		offsets:          offsets,
		eventFeed:        feed,
		logger:           log,
		lastVulnCreateID: 0,
	}
}

func (rc *AlertRuntimeControl) Start() (RuntimeStatus, error) {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	if rc.isRunning {
		return rc.statusLocked(), nil
	}
	if rc.service == nil || rc.rulesRepo == nil || rc.eventFeed == nil {
		return rc.statusLocked(), fmt.Errorf("alert runtime not configured")
	}

	rc.cronScheduler = cron.New()
	if err := rc.configureLocked(); err != nil {
		rc.lastError = err.Error()
		now := time.Now().UTC()
		rc.lastErrorAt = &now
		return rc.statusLocked(), err
	}
	rc.cronScheduler.Start()
	rc.isRunning = true
	rc.clearErrorLocked()
	return rc.statusLocked(), nil
}

func (rc *AlertRuntimeControl) Stop() RuntimeStatus {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	if rc.cronScheduler != nil {
		ctx := rc.cronScheduler.Stop()
		select {
		case <-ctx.Done():
		case <-time.After(5 * time.Second):
		}
	}
	rc.cronScheduler = nil
	rc.entryIDs = nil
	rc.isRunning = false
	return rc.statusLocked()
}

func (rc *AlertRuntimeControl) StopWithDiagnostic(message string) RuntimeStatus {
	rc.Stop()

	rc.mu.Lock()
	defer rc.mu.Unlock()
	if strings.TrimSpace(message) != "" {
		rc.lastError = message
		now := time.Now().UTC()
		rc.lastErrorAt = &now
	}
	return rc.statusLocked()
}

func (rc *AlertRuntimeControl) Refresh() (RuntimeStatus, error) {
	rc.mu.Lock()
	defer rc.mu.Unlock()
	if !rc.isRunning || rc.cronScheduler == nil {
		return rc.statusLocked(), nil
	}
	for _, id := range rc.entryIDs {
		rc.cronScheduler.Remove(id)
	}
	rc.entryIDs = nil
	if err := rc.configureLocked(); err != nil {
		rc.lastError = err.Error()
		now := time.Now().UTC()
		rc.lastErrorAt = &now
		return rc.statusLocked(), err
	}
	rc.clearErrorLocked()
	return rc.statusLocked(), nil
}

func (rc *AlertRuntimeControl) Status() RuntimeStatus {
	rc.mu.RLock()
	defer rc.mu.RUnlock()
	return rc.statusLocked()
}

func (rc *AlertRuntimeControl) statusLocked() RuntimeStatus {
	status := RuntimeStatus{
		SchedulerRunning: rc.isRunning && rc.cronScheduler != nil,
		ScheduledRules:   rc.countScheduledRulesLocked(),
	}
	if rc.lastTickAt != nil {
		status.LastTickAt = rc.lastTickAt.Format(time.RFC3339)
	}
	if rc.lastErrorAt != nil {
		status.LastErrorAt = rc.lastErrorAt.Format(time.RFC3339)
	}
	if rc.lastError != "" {
		status.LastError = rc.lastError
	}
	return status
}

func (rc *AlertRuntimeControl) countScheduledRulesLocked() int {
	count := len(rc.entryIDs)
	if count > 0 {
		return count - 1
	}
	return 0
}

func (rc *AlertRuntimeControl) configureLocked() error {
	ctx := context.Background()
	if err := rc.restoreOffsetsLocked(ctx); err != nil {
		return err
	}
	rules, err := rc.rulesRepo.List(ctx)
	if err != nil {
		return err
	}

	scheduled := 0
	for _, rule := range rules {
		if !rule.Enabled {
			continue
		}
		cronExpr := strings.TrimSpace(rule.CronExpr)
		if cronExpr == "" {
			continue
		}
		ruleCopy := rule
		entryID, err := rc.cronScheduler.AddFunc(cronExpr, func() {
			_, runErr := rc.service.RunForCron(context.Background(), &ruleCopy)
			if runErr != nil {
				rc.noteError(runErr)
			}
		})
		if err != nil {
			return fmt.Errorf("add rule %d cron: %w", rule.ID, err)
		}
		rc.entryIDs = append(rc.entryIDs, entryID)
		scheduled++
	}

	pollerID, err := rc.cronScheduler.AddFunc("@every 1m", func() {
		rc.tick()
	})
	if err != nil {
		return fmt.Errorf("add event poller: %w", err)
	}
	rc.entryIDs = append(rc.entryIDs, pollerID)

	if rc.logger != nil {
		rc.logger.Info("alert runtime configured", "scheduled_rules", scheduled)
	}
	return nil
}

func (rc *AlertRuntimeControl) restoreOffsetsLocked(ctx context.Context) error {
	if rc.offsets == nil {
		return nil
	}
	vulnCreateID, err := rc.offsets.Get(ctx, alertOffsetVulnerabilityCreate)
	if err != nil {
		return err
	}
	vulnEventID, err := rc.offsets.Get(ctx, alertOffsetVulnerabilityEvent)
	if err != nil {
		return err
	}
	assetHistoryID, err := rc.offsets.Get(ctx, alertOffsetAssetHistory)
	if err != nil {
		return err
	}
	rc.lastVulnCreateID = vulnCreateID
	rc.lastVulnEventID = vulnEventID
	rc.lastAssetHistoryID = assetHistoryID
	return nil
}

func (rc *AlertRuntimeControl) tick() {
	now := time.Now().UTC()
	rc.mu.Lock()
	rc.lastTickAt = &now
	rc.mu.Unlock()

	if err := rc.processVulnerabilityCreates(); err != nil {
		rc.noteError(err)
	}
	if err := rc.processVulnerabilityEvents(); err != nil {
		rc.noteError(err)
	}
	if err := rc.processAssetHistory(); err != nil {
		rc.noteError(err)
	}
}

func (rc *AlertRuntimeControl) noteError(err error) {
	if err == nil {
		return
	}
	rc.mu.Lock()
	defer rc.mu.Unlock()
	rc.lastError = err.Error()
	now := time.Now().UTC()
	rc.lastErrorAt = &now
}

func (rc *AlertRuntimeControl) clearErrorLocked() {
	rc.lastError = ""
	rc.lastErrorAt = nil
}

func (rc *AlertRuntimeControl) processVulnerabilityCreates() error {
	ctx := context.Background()
	rc.mu.RLock()
	lastID := rc.lastVulnCreateID
	rc.mu.RUnlock()

	items, err := rc.eventFeed.ListVulnerabilityCreates(ctx, lastID, 200)
	if err != nil {
		return err
	}
	if len(items) == 0 {
		return nil
	}

	maxID := lastID
	for i := range items {
		if items[i].ID > maxID {
			maxID = items[i].ID
		}
		_ = rc.runEventRules("vulnerability", "create", items[i].ID)
	}
	if err := rc.advanceOffset(ctx, alertOffsetVulnerabilityCreate, maxID); err != nil {
		return err
	}
	rc.mu.Lock()
	rc.lastVulnCreateID = maxID
	rc.mu.Unlock()
	return nil
}

func (rc *AlertRuntimeControl) processVulnerabilityEvents() error {
	ctx := context.Background()
	rc.mu.RLock()
	lastID := rc.lastVulnEventID
	rc.mu.RUnlock()

	events, err := rc.eventFeed.ListVulnerabilityEvents(ctx, lastID, 500)
	if err != nil {
		return err
	}
	if len(events) == 0 {
		return nil
	}
	maxID := lastID
	for i := range events {
		if events[i].ID > maxID {
			maxID = events[i].ID
		}
		switch strings.ToLower(strings.TrimSpace(events[i].EventType)) {
		case "status_changed":
			_ = rc.runEventRules("vulnerability", "status_change", events[i].VulnerabilityID)
		case "claimed", "owner_changed":
			_ = rc.runEventRules("vulnerability", "update", events[i].VulnerabilityID)
		}
	}
	if err := rc.advanceOffset(ctx, alertOffsetVulnerabilityEvent, maxID); err != nil {
		return err
	}
	rc.mu.Lock()
	rc.lastVulnEventID = maxID
	rc.mu.Unlock()
	return nil
}

func (rc *AlertRuntimeControl) processAssetHistory() error {
	ctx := context.Background()
	rc.mu.RLock()
	lastID := rc.lastAssetHistoryID
	rc.mu.RUnlock()

	history, err := rc.eventFeed.ListAssetHistoryChanges(ctx, lastID, 500)
	if err != nil {
		return err
	}
	if len(history) == 0 {
		return nil
	}

	maxID := lastID
	for i := range history {
		if history[i].ID > maxID {
			maxID = history[i].ID
		}
		switch strings.ToLower(strings.TrimSpace(history[i].Action)) {
		case "create":
			_ = rc.runEventRules("asset", "create", history[i].AssetID)
		case "update":
			_ = rc.runEventRules("asset", "update", history[i].AssetID)
		}
	}
	if err := rc.advanceOffset(ctx, alertOffsetAssetHistory, maxID); err != nil {
		return err
	}
	rc.mu.Lock()
	rc.lastAssetHistoryID = maxID
	rc.mu.Unlock()
	return nil
}

func (rc *AlertRuntimeControl) advanceOffset(ctx context.Context, key string, lastProcessedID uint64) error {
	if rc.offsets == nil {
		return nil
	}
	return rc.offsets.Advance(ctx, key, lastProcessedID)
}

func (rc *AlertRuntimeControl) runEventRules(entityType string, eventKind string, entityID uint64) error {
	rules, err := rc.rulesRepo.List(context.Background())
	if err != nil {
		return err
	}
	for i := range rules {
		rule := rules[i]
		if !rule.Enabled {
			continue
		}
		if strings.ToLower(rule.EntityType) != strings.ToLower(entityType) {
			continue
		}
		if !ruleMatchesEvent(rule, eventKind) {
			continue
		}
		ruleCopy := rule
		_, runErr := rc.service.RunForEvent(context.Background(), &ruleCopy, entityType, entityID)
		if runErr != nil {
			rc.noteError(runErr)
		}
	}
	return nil
}

func ruleMatchesEvent(rule alertmodel.LarkAlertRule, eventKind string) bool {
	switch eventKind {
	case "create":
		return rule.EventOnCreate
	case "update":
		return rule.EventOnUpdate
	case "status_change":
		return rule.EventOnStatusChange
	case "severity_change":
		return rule.EventOnSeverityChange
	default:
		return false
	}
}
