package worker

import (
	"context"
	"fmt"
	"strings"

	agentdomain "github.com/codex-k8s/kodex/libs/go/domain/agent"
	floweventdomain "github.com/codex-k8s/kodex/libs/go/domain/flowevent"
	rundomain "github.com/codex-k8s/kodex/libs/go/domain/run"
	floweventrepo "github.com/codex-k8s/kodex/services/jobs/worker/internal/domain/repository/flowevent"
	runqueuerepo "github.com/codex-k8s/kodex/services/jobs/worker/internal/domain/repository/runqueue"
	valuetypes "github.com/codex-k8s/kodex/services/jobs/worker/internal/domain/types/value"
)

func (s *Service) launchPreparedRunWorkload(ctx context.Context, run runqueuerepo.RunningRun, execution valuetypes.RunExecutionContext, agentCtx runAgentContext, lease namespaceLeaseSpec, options runLaunchOptions) error {
	runtimePayload := parseRunRuntimePayload(run.RunPayload)
	runtimeTargetEnv := ""
	runtimeBuildRef := ""
	runtimeAccessProfile := options.RuntimeAccessProfile
	if runtimePayload.Runtime != nil {
		runtimeTargetEnv = strings.TrimSpace(runtimePayload.Runtime.TargetEnv)
		runtimeBuildRef = strings.TrimSpace(runtimePayload.Runtime.BuildRef)
		if runtimeAccessProfile == "" {
			runtimeAccessProfile = resolveRuntimeAccessProfile(runtimePayload)
		}
	}

	namespaceSpec := NamespaceSpec{
		RunID:         run.RunID,
		ProjectID:     run.ProjectID,
		IssueNumber:   lease.IssueNumber,
		AgentKey:      lease.AgentKey,
		CorrelationID: run.CorrelationID,
		RuntimeMode:   execution.RuntimeMode,
		Namespace:     execution.Namespace,
		AccessProfile: runtimeAccessProfile,
	}

	if shouldManageRunNamespace(execution, agentCtx) && !options.SkipNamespacePreparation {
		ttl := lease.TTL
		if ttl <= 0 {
			ttl = s.cfg.DefaultNamespaceTTL
		}
		namespaceSpec.LeaseTTL = ttl
		namespaceSpec.LeaseExpiresAt = s.now().UTC().Add(ttl)

		ensureResult, err := s.launcher.EnsureNamespace(ctx, namespaceSpec)
		if err != nil {
			s.logger.Error(
				"prepare run namespace failed",
				"run_id", run.RunID,
				"namespace", execution.Namespace,
				"runtime_mode", execution.RuntimeMode,
				"err", err,
			)
			if finishErr := s.finishLaunchFailedRun(ctx, run, execution, err, runFailureReasonNamespacePrepareFailed); finishErr != nil {
				return fmt.Errorf("mark run failed after namespace prepare error: %w", finishErr)
			}
			return nil
		}
		leaseExpiresAt := ensureResult.LeaseExpiresAt
		if leaseExpiresAt.IsZero() {
			leaseExpiresAt = namespaceSpec.LeaseExpiresAt
		}

		if err := s.insertNamespaceLifecycleEvent(ctx, namespaceLifecycleEventParams{
			CorrelationID: run.CorrelationID,
			EventType:     floweventdomain.EventTypeRunNamespacePrepared,
			RunID:         run.RunID,
			ProjectID:     run.ProjectID,
			Execution:     execution,
		}); err != nil {
			return fmt.Errorf("insert run.namespace.prepared event: %w", err)
		}

		namespaceTTLEventType := floweventdomain.EventTypeRunNamespaceTTLScheduled
		if ensureResult.Reused {
			namespaceTTLEventType = floweventdomain.EventTypeRunNamespaceTTLExtended
		}
		if err := s.insertNamespaceLifecycleEvent(ctx, namespaceLifecycleEventParams{
			CorrelationID: run.CorrelationID,
			EventType:     namespaceTTLEventType,
			RunID:         run.RunID,
			ProjectID:     run.ProjectID,
			Execution:     execution,
			Extra: namespaceLifecycleEventExtra{
				NamespaceLeaseTTL:       ttl,
				NamespaceLeaseExpiresAt: leaseExpiresAt,
				NamespaceReused:         ensureResult.Reused,
			},
		}); err != nil {
			return fmt.Errorf("insert %s event: %w", namespaceTTLEventType, err)
		}
	}
	if options.SkipNamespacePreparation && execution.RuntimeMode == agentdomain.RuntimeModeFullEnv && runtimeAccessProfile != "" {
		serviceAccountName, err := s.launcher.EnsureAccessProfile(ctx, execution.Namespace, runtimeAccessProfile)
		if err != nil {
			s.logger.Error(
				"prepare run access profile failed",
				"run_id", run.RunID,
				"namespace", execution.Namespace,
				"access_profile", runtimeAccessProfile,
				"err", err,
			)
			if finishErr := s.finishLaunchFailedRun(ctx, run, execution, err, runFailureReasonNamespacePrepareFailed); finishErr != nil {
				return fmt.Errorf("mark run failed after access profile prepare error: %w", finishErr)
			}
			return nil
		}
		if strings.TrimSpace(options.ServiceAccountName) == "" {
			options.ServiceAccountName = serviceAccountName
		}
	}

	issuedMCPToken, err := s.mcpTokens.IssueRunMCPToken(ctx, IssueMCPTokenParams{
		RunID:       run.RunID,
		Namespace:   execution.Namespace,
		RuntimeMode: execution.RuntimeMode,
	})
	if err != nil {
		s.logger.Error("issue run mcp token failed", "run_id", run.RunID, "err", err)
		if finishErr := s.finishLaunchFailedRun(ctx, run, execution, err, runFailureReasonMCPTokenIssueFailed); finishErr != nil {
			return fmt.Errorf("mark run failed after mcp token issue error: %w", finishErr)
		}
		return nil
	}

	if _, err := s.runStatus.UpsertRunStatusComment(ctx, RunStatusCommentParams{
		RunID:           run.RunID,
		Phase:           RunStatusPhaseCreated,
		RuntimeMode:     string(execution.RuntimeMode),
		Namespace:       execution.Namespace,
		TriggerKind:     agentCtx.TriggerKind,
		PromptLocale:    agentCtx.PromptTemplateLocale,
		Model:           agentCtx.Model,
		ReasoningEffort: agentCtx.ReasoningEffort,
		RunStatus:       string(rundomain.StatusRunning),
	}); err != nil {
		s.logger.Warn("upsert run status comment (created) failed", "run_id", run.RunID, "err", err)
	}

	if err := s.insertEvent(ctx, floweventrepo.InsertParams{
		CorrelationID: run.CorrelationID,
		ActorType:     floweventdomain.ActorTypeSystem,
		ActorID:       floweventdomain.ActorID(s.cfg.WorkerID),
		EventType:     floweventdomain.EventTypeRunProfileResolved,
		Payload: encodeRunProfileResolvedEventPayload(runProfileResolvedEventPayload{
			RunID:              run.RunID,
			ProjectID:          run.ProjectID,
			RepositoryFullName: agentCtx.RepositoryFullName,
			IssueNumber:        agentCtx.IssueNumber,
			PullRequestNumber:  agentCtx.ExistingPRNumber,
			TriggerKind:        agentCtx.TriggerKind,
			DiscussionMode:     agentCtx.DiscussionMode,
			Model:              agentCtx.Model,
			ModelSource:        agentCtx.ModelSource,
			ReasoningEffort:    agentCtx.ReasoningEffort,
			ReasoningSource:    agentCtx.ReasoningSource,
		}),
		CreatedAt: s.now().UTC(),
	}); err != nil {
		return fmt.Errorf("insert run.profile.resolved event: %w", err)
	}

	jobImage := s.resolveRunJobImage(ctx)
	if jobImage.EmitEvent {
		if err := s.insertEvent(ctx, floweventrepo.InsertParams{
			CorrelationID: run.CorrelationID,
			ActorType:     floweventdomain.ActorTypeSystem,
			ActorID:       floweventdomain.ActorID(s.cfg.WorkerID),
			EventType:     floweventdomain.EventTypeRunJobImageResolved,
			Payload: encodeRunJobImageResolvedEventPayload(runJobImageResolvedEventPayload{
				RunID:             run.RunID,
				ProjectID:         run.ProjectID,
				SelectedImage:     jobImage.SelectedImage,
				SelectedSource:    jobImage.SelectedSource,
				PrimaryImage:      jobImage.PrimaryImage,
				FallbackImage:     jobImage.FallbackImage,
				PrimaryAvailable:  jobImage.PrimaryAvailable,
				FallbackAvailable: jobImage.FallbackAvailable,
				CheckError:        jobImage.CheckError,
			}),
			CreatedAt: s.now().UTC(),
		}); err != nil {
			return fmt.Errorf("insert run.job.image.resolved event: %w", err)
		}
	}

	targetBranch := strings.TrimSpace(agentCtx.TargetBranch)
	if isAIRepairTriggerKind(agentCtx.TriggerKind) && targetBranch == "" {
		targetBranch = strings.TrimSpace(s.cfg.AgentBaseBranch)
	}

	ref, err := s.launcher.Launch(ctx, JobSpec{
		RunID:                    run.RunID,
		CorrelationID:            run.CorrelationID,
		ProjectID:                run.ProjectID,
		SlotNo:                   run.SlotNo,
		JobImage:                 jobImage.SelectedImage,
		RuntimeMode:              execution.RuntimeMode,
		Namespace:                execution.Namespace,
		RuntimeTargetEnv:         runtimeTargetEnv,
		RuntimeBuildRef:          runtimeBuildRef,
		RuntimeAccessProfile:     runtimeAccessProfile,
		ControlPlaneGRPCTarget:   resolveRunControlPlaneGRPCTarget(s.cfg.ProductionNamespace, s.cfg.ControlPlaneGRPCTarget),
		MCPBaseURL:               s.cfg.ControlPlaneMCPBaseURL,
		MCPBearerToken:           issuedMCPToken.Token,
		QualityGovernanceEnabled: s.qualityGovernanceEnabled(),
		RepositoryFullName:       agentCtx.RepositoryFullName,
		IssueNumber:              agentCtx.IssueNumber,
		TriggerKind:              agentCtx.TriggerKind,
		TriggerLabel:             agentCtx.TriggerLabel,
		DiscussionMode:           agentCtx.DiscussionMode,
		TargetBranch:             targetBranch,
		ExistingPRNumber:         agentCtx.ExistingPRNumber,
		AgentKey:                 agentCtx.AgentKey,
		AgentModel:               agentCtx.Model,
		AgentReasoningEffort:     agentCtx.ReasoningEffort,
		PromptTemplateKind:       agentCtx.PromptTemplateKind,
		PromptTemplateSource:     agentCtx.PromptTemplateSource,
		PromptTemplateLocale:     agentCtx.PromptTemplateLocale,
		StateInReviewLabel:       s.cfg.StateInReviewLabel,
		BaseBranch:               s.cfg.AgentBaseBranch,
		OpenAIAPIKey:             s.cfg.OpenAIAPIKey,
		Context7APIKey:           s.cfg.Context7APIKey,
		GitBotToken:              s.cfg.GitBotToken,
		AgentDisplayName:         agentCtx.AgentDisplayName,
		GitBotUsername:           s.cfg.GitBotUsername,
		GitBotMail:               s.cfg.GitBotMail,
		ServiceAccountName:       strings.TrimSpace(options.ServiceAccountName),
	})
	if err != nil {
		s.logger.Error("launch run job failed", "run_id", run.RunID, "err", err)
		if finishErr := s.finishRun(ctx, finishRunParams{
			Run:       run,
			Execution: execution,
			Status:    rundomain.StatusFailed,
			EventType: floweventdomain.EventTypeRunFailedLaunchError,
			Ref:       ref,
			Extra: runFinishedEventExtra{
				Error: err.Error(),
			},
		}); finishErr != nil {
			return fmt.Errorf("mark run failed after launch error: %w", finishErr)
		}
		return nil
	}

	if err := s.insertEvent(ctx, floweventrepo.InsertParams{
		CorrelationID: run.CorrelationID,
		ActorType:     floweventdomain.ActorTypeSystem,
		ActorID:       floweventdomain.ActorID(s.cfg.WorkerID),
		EventType:     floweventdomain.EventTypeRunStarted,
		Payload: encodeRunStartedEventPayload(runStartedEventPayload{
			RunID:                run.RunID,
			ProjectID:            run.ProjectID,
			SlotNo:               run.SlotNo,
			JobName:              ref.Name,
			JobNamespace:         ref.Namespace,
			RuntimeMode:          execution.RuntimeMode,
			RepositoryFullName:   agentCtx.RepositoryFullName,
			AgentKey:             agentCtx.AgentKey,
			IssueNumber:          agentCtx.IssueNumber,
			TriggerKind:          agentCtx.TriggerKind,
			TriggerLabel:         agentCtx.TriggerLabel,
			DiscussionMode:       agentCtx.DiscussionMode,
			JobImage:             jobImage.SelectedImage,
			Model:                agentCtx.Model,
			ModelSource:          agentCtx.ModelSource,
			ReasoningEffort:      agentCtx.ReasoningEffort,
			ReasoningSource:      agentCtx.ReasoningSource,
			PromptTemplateKind:   agentCtx.PromptTemplateKind,
			PromptTemplateSource: agentCtx.PromptTemplateSource,
			PromptTemplateLocale: agentCtx.PromptTemplateLocale,
			BaseBranch:           s.cfg.AgentBaseBranch,
		}),
		CreatedAt: s.now().UTC(),
	}); err != nil {
		return fmt.Errorf("insert run.started event: %w", err)
	}

	if _, err := s.runStatus.UpsertRunStatusComment(ctx, RunStatusCommentParams{
		RunID:           run.RunID,
		Phase:           RunStatusPhaseStarted,
		JobName:         ref.Name,
		JobNamespace:    ref.Namespace,
		RuntimeMode:     string(execution.RuntimeMode),
		Namespace:       execution.Namespace,
		TriggerKind:     agentCtx.TriggerKind,
		PromptLocale:    agentCtx.PromptTemplateLocale,
		Model:           agentCtx.Model,
		ReasoningEffort: agentCtx.ReasoningEffort,
		RunStatus:       string(rundomain.StatusRunning),
	}); err != nil {
		s.logger.Warn("upsert run status comment (started) failed", "run_id", run.RunID, "err", err)
	}

	return nil
}

func shouldManageRunNamespace(execution valuetypes.RunExecutionContext, agentCtx runAgentContext) bool {
	if strings.TrimSpace(execution.Namespace) == "" {
		return false
	}
	if execution.RuntimeMode == agentdomain.RuntimeModeFullEnv {
		return true
	}
	return execution.RuntimeMode == agentdomain.RuntimeModeCodeOnly && agentCtx.DiscussionMode
}
