package modules

import (
	"context"
	"time"

	redisclient "github.com/redis/go-redis/v9"
	"gorm.io/gorm"

	caasmhandler "security-center-backend/internal/modules/caasm/handler"
	caasmmodel "security-center-backend/internal/modules/caasm/model"
	caasmqueue "security-center-backend/internal/modules/caasm/queue"
	caasmrepository "security-center-backend/internal/modules/caasm/repository"
	caasmservice "security-center-backend/internal/modules/caasm/service"
	"security-center-backend/internal/pkg/cache"
	"security-center-backend/internal/pkg/logger"
	"security-center-backend/internal/repository"
	"security-center-backend/internal/service/audit"
)

type CAASMModule struct {
	Handler                 *caasmhandler.Handler
	QueueMetricsProvider    func(ctx context.Context) (*caasmqueue.QueueMetrics, error)
	SchedulerStatusProvider func() caasmservice.SchedulerStatus
	SyncJobTimeoutProvider  func(ctx context.Context) (caasmservice.SyncJobTimeouts, error)
	StopHooks               []StopHook
}

type CAASMBuildInput struct {
	DB                   *gorm.DB
	Redis                *redisclient.Client
	AssetRepo            caasmrepository.AssetRepository
	PortRepo             caasmrepository.IPPortIndexRepository
	DomainRepo           caasmrepository.DomainRepository
	DNSRecordRepo        caasmrepository.DNSRecordRepository
	DNSRecordCommentRepo caasmrepository.DNSRecordCommentRepository
	CloudAccountRepo     caasmrepository.CloudAccountRepository
	SyncJobRepo          caasmrepository.SyncJobRepository
	SyncJobEventRepo     caasmrepository.SyncJobEventRepository
	ConfigRepo           caasmrepository.ConfigRepository
	APIRouteAssetRepo    caasmrepository.APIRouteAssetRepository
	HistoryRepo          caasmrepository.AssetHistoryRepository
	TagRepo              caasmrepository.AssetTagRepository
	Cache                *cache.Cache
	Logger               *logger.Logger
	Audit                *audit.Service
	UserRepo             repository.UserRepository
}

var newCAASMJobQueue = caasmqueue.NewJobQueue

func BuildCAASM(input CAASMBuildInput) *CAASMModule {
	if input.AssetRepo == nil {
		return &CAASMModule{}
	}

	assetSvc := caasmservice.NewAssetService(input.AssetRepo, input.UserRepo)
	portSvc := caasmservice.NewPortService(input.PortRepo)
	domainSvc := caasmservice.NewDomainService(input.DomainRepo, input.AssetRepo, input.UserRepo)
	dnsRecordSvc := caasmservice.NewDNSRecordService(input.DNSRecordRepo, input.DNSRecordCommentRepo, input.UserRepo)
	syncJobSvc := caasmservice.NewSyncJobService(input.SyncJobRepo, input.SyncJobEventRepo)
	var configSvc *caasmservice.ConfigService
	if input.ConfigRepo != nil {
		configSvc = caasmservice.NewConfigService(input.ConfigRepo)
	}
	apiRouteAssetRepo := input.APIRouteAssetRepo
	if apiRouteAssetRepo == nil && input.DB != nil {
		apiRouteAssetRepo = caasmrepository.NewAPIRouteAssetRepository(input.DB)
	}
	apiRouteAssetSvc := caasmservice.NewAPIRouteAssetService(apiRouteAssetRepo)

	cloudAcctSvc := caasmservice.NewCloudAccountService(input.CloudAccountRepo, input.SyncJobRepo, input.AssetRepo)
	cloudAcctSvc.SetPortRepository(input.PortRepo)
	cloudAcctSvc.SetDNSRecordRepository(input.DNSRecordRepo)
	cloudAcctSvc.SetHistoryRepository(input.HistoryRepo)
	cloudAcctSvc.SetTagRepository(input.TagRepo)
	cloudAcctSvc.SetEventRepository(input.SyncJobEventRepo)
	cloudAcctSvc.SetConfigService(configSvc)
	cloudAcctSvc.SetLogger(input.Logger)

	var jobQueue *caasmqueue.JobQueue
	var queueInitErr error
	if input.Redis != nil {
		if queue, err := newCAASMJobQueue(input.Redis, "caasm:sync-jobs", "caasm-workers"); err == nil {
			jobQueue = queue
			jobQueue.SetStuckJobGuard(caasmStuckJobGuard(input.SyncJobRepo, configSvc))
			cloudAcctSvc.SetJobQueue(jobQueue)
		} else {
			queueInitErr = err
			if input.Logger != nil {
				input.Logger.Error("caasm job queue initialization failed; queue workers disabled",
					"error", err,
					"stream", "caasm:sync-jobs",
					"consumer_group", "caasm-workers",
				)
			}
		}
	}

	executor := func(ctx context.Context, payload *caasmqueue.SyncJobPayload) error {
		return cloudAcctSvc.ExecuteSyncPayload(ctx, payload)
	}
	var runtimeQueue caasmqueue.JobQueueInterface
	if jobQueue != nil {
		runtimeQueue = jobQueue
	}
	throttlingProvider := func(ctx context.Context) (caasmservice.CoordinationCap, error) {
		if configSvc == nil {
			return caasmservice.CoordinationCap{}, nil
		}
		resolution, err := configSvc.ResolveEffectiveThrottling(ctx)
		if err != nil {
			return caasmservice.CoordinationCap{}, err
		}
		return caasmservice.CoordinationCap{
			Config: resolution.Effective,
			Source: resolution.Sources["max_concurrency"],
		}, nil
	}
	scheduler := caasmservice.NewCollectorScheduler(input.CloudAccountRepo, cloudAcctSvc, input.SyncJobRepo, runtimeQueue, input.Logger, 6*time.Hour, throttlingProvider)
	runtimeCtrl := caasmservice.NewRuntimeControl(runtimeQueue, 5, executor, scheduler, input.SyncJobRepo, throttlingProvider, input.Logger)
	if queueInitErr != nil {
		runtimeCtrl.SetQueueDegraded(queueInitErr)
	}
	if configSvc != nil {
		runtimeCtrl.SetKillSwitchProvider(configSvc.KillSwitchEnabled)
	}
	runtimeCtrl.SetAdmissionController(func(ctx context.Context, job *caasmqueue.SyncJobPayload, activeLocalJobs int) caasmqueue.AdmissionDecision {
		if input.SyncJobRepo == nil {
			return caasmqueue.AdmissionDecision{Allowed: true}
		}
		if activeJob, err := input.SyncJobRepo.FindActiveJobForAccount(ctx, job.CloudAccountID); err == nil && activeJob != nil {
			if job.SyncJobID == nil || activeJob.ID != *job.SyncJobID {
				return caasmqueue.AdmissionDecision{
					Allowed: false,
					Delay:   20 * time.Second,
					Reason:  "db_active_account_guard",
					Metadata: map[string]interface{}{
						"slowdown_source":    "coordination_guard",
						"coordination_state": "db_active_account_guard",
						"active_job_id":      activeJob.ID,
						"active_job_status":  activeJob.Status,
						"active_local_jobs":  activeLocalJobs,
					},
				}
			}
		}
		capResult, err := throttlingProvider(ctx)
		if err != nil {
			return caasmqueue.AdmissionDecision{Allowed: true}
		}
		runtimeCap := capResult.Config.MaxConcurrency
		if runtimeCap <= 0 {
			return caasmqueue.AdmissionDecision{Allowed: true}
		}
		runningJobs, err := input.SyncJobRepo.CountRunningJobs(ctx)
		if err != nil {
			return caasmqueue.AdmissionDecision{Allowed: true}
		}
		if runningJobs < int64(runtimeCap) {
			return caasmqueue.AdmissionDecision{Allowed: true}
		}
		return caasmqueue.AdmissionDecision{
			Allowed: false,
			Delay:   20 * time.Second,
			Reason:  "runtime_capacity_guard",
			Metadata: map[string]interface{}{
				"slowdown_source":     "coordination_guard",
				"coordination_state":  "runtime_capacity_guard",
				"active_running_jobs": runningJobs,
				"runtime_cap":         runtimeCap,
				"limit_source":        capResult.Source,
				"active_local_jobs":   activeLocalJobs,
			},
		}
	})
	runtimeCtrl.SetEventReporter(func(ctx context.Context, syncJobID uint64, eventType string, message string, metadata map[string]interface{}) {
		_ = syncJobSvc.ReportEvent(ctx, syncJobID, eventType, message, metadata)
	})

	webhookHandler := caasmhandler.NewWebhookHandler(cloudAcctSvc, runtimeQueue, input.Logger, input.CloudAccountRepo)
	handler := caasmhandler.New(assetSvc, portSvc, domainSvc, dnsRecordSvc, cloudAcctSvc, syncJobSvc, webhookHandler, configSvc, apiRouteAssetSvc, runtimeCtrl, input.Audit, input.Cache)

	module := &CAASMModule{
		Handler: handler,
		StopHooks: []StopHook{
			func(ctx context.Context) error {
				runtimeCtrl.Stop()
				return nil
			},
			func(ctx context.Context) error {
				scheduler.Stop()
				return nil
			},
		},
		SchedulerStatusProvider: scheduler.GetStatus,
	}
	if configSvc != nil {
		module.SyncJobTimeoutProvider = func(ctx context.Context) (caasmservice.SyncJobTimeouts, error) {
			settings, err := configSvc.ResolveSyncSettings(ctx)
			if err != nil {
				return caasmservice.DefaultSyncJobTimeouts(), err
			}
			return caasmservice.SyncJobTimeoutsFromSettings(settings), nil
		}
	}

	if jobQueue != nil {
		module.QueueMetricsProvider = func(ctx context.Context) (*caasmqueue.QueueMetrics, error) {
			queueMetrics, err := jobQueue.GetMetrics(ctx)
			if err != nil {
				return nil, err
			}
			workerMetrics := runtimeCtrl.WorkerMetrics()
			merged := caasmqueue.MergeMetrics(queueMetrics, workerMetrics)
			if input.DB != nil {
				collectorMetrics, err := caasmqueue.GetCollectorMetrics(ctx, input.DB)
				if err == nil {
					merged.Collector = collectorMetrics
				}
			}
			return merged, nil
		}
	}

	return module
}

func caasmStuckJobGuard(syncJobs caasmrepository.SyncJobRepository, configSvc *caasmservice.ConfigService) caasmqueue.StuckJobGuard {
	return func(ctx context.Context, payload *caasmqueue.SyncJobPayload, idle time.Duration) caasmqueue.StuckJobDecision {
		if syncJobs == nil || payload == nil || payload.SyncJobID == nil {
			return caasmqueue.StuckJobDecision{}
		}
		job, err := syncJobs.FindByID(ctx, *payload.SyncJobID)
		if err != nil || job == nil {
			return caasmqueue.StuckJobDecision{}
		}
		if !caasmSyncJobActiveForStuckGuard(job.Status) {
			return caasmqueue.StuckJobDecision{}
		}

		timeout := time.Duration(caasmmodel.DefaultSyncJobExecutionTimeoutMinutes) * time.Minute
		if configSvc != nil {
			if settings, err := configSvc.ResolveSyncSettings(ctx); err == nil && settings.ExecutionTimeoutMinutes > 0 {
				timeout = time.Duration(settings.ExecutionTimeoutMinutes) * time.Minute
			}
		}
		startedAt := job.CreatedAt
		if job.StartedAt != nil && !job.StartedAt.IsZero() {
			startedAt = *job.StartedAt
		} else if !job.UpdatedAt.IsZero() {
			startedAt = job.UpdatedAt
		}
		if startedAt.IsZero() || time.Since(startedAt) <= timeout {
			return caasmqueue.StuckJobDecision{
				SkipRetry: true,
				Reason:    "db_sync_job_still_active",
				Metadata: map[string]interface{}{
					"sync_job_id":       job.ID,
					"sync_job_status":   job.Status,
					"job_idle_seconds":  int(idle.Seconds()),
					"timeout_seconds":   int(timeout.Seconds()),
					"stuck_guard_scope": "sync_job_status",
				},
			}
		}
		return caasmqueue.StuckJobDecision{}
	}
}

func caasmSyncJobActiveForStuckGuard(status caasmmodel.SyncJobStatus) bool {
	return status == caasmmodel.SyncJobQueued ||
		status == caasmmodel.SyncJobPending ||
		status == caasmmodel.SyncJobRunning
}
