package server

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/automation/blob"
	"github.com/tidbcloud/AgentTeam/internal/config"
	automationservice "github.com/tidbcloud/AgentTeam/internal/service/automation"
	"github.com/tidbcloud/AgentTeam/internal/service/automation/runtime"
	automationstore "github.com/tidbcloud/AgentTeam/internal/store/automation"
)

type automationCheckoutMaterializer struct {
	legacy runtime.CheckoutMaterializer
	native runtime.CheckoutMaterializer
}

func (materializer *automationCheckoutMaterializer) Materialize(ctx context.Context, request runtime.CheckoutRequest) (runtime.CheckoutResult, error) {
	if materializer == nil {
		return runtime.CheckoutResult{}, fmt.Errorf("automation checkout is not configured")
	}
	contentID := strings.TrimSpace(request.ContentID)
	versionID := strings.TrimSpace(request.ContentVersionID)
	if contentID != "" || versionID != "" {
		if contentID == "" || versionID == "" || materializer.native == nil {
			return runtime.CheckoutResult{}, fmt.Errorf("native automation checkout is not configured")
		}
		return materializer.native.Materialize(ctx, request)
	}
	if materializer.legacy == nil {
		return runtime.CheckoutResult{}, fmt.Errorf("legacy automation checkout is not configured")
	}
	return materializer.legacy.Materialize(ctx, request)
}

func automationLimitsFromConfig(cfg config.AutomationConfig) automationservice.AutomationLimits {
	return automationservice.AutomationLimits{
		MaxAutomationSourceBytes:  cfg.MaxAutomationSourceBytes,
		MaxAutomationJobs:         cfg.MaxAutomationJobs,
		MaxAutomationStepsPerJob:  cfg.MaxAutomationStepsPerJob,
		MaxRunInputBytes:          cfg.MaxRunInputBytes,
		MaxJobInputBytes:          cfg.MaxJobInputBytes,
		MaxLogChunkBytes:          cfg.MaxLogChunkBytes,
		MaxArtifactBytes:          cfg.MaxArtifactBytes,
		MaxRunnerLabels:           cfg.MaxRunnerLabels,
		MaxActiveRunsPerWorkspace: cfg.MaxActiveRunsPerWorkspace,
	}
}

func automationAuthorizerOption(cfg config.ResourceRuntimeConfig, options []Options) automationservice.AutomationAuthorizer {
	if len(options) > 0 && options[0].AutomationAuthorizer != nil {
		return options[0].AutomationAuthorizer
	}
	if cfg.Auth.TrustedJWTClaims {
		return automationservice.ClaimAutomationAuthorizer{}
	}
	return nil
}

func automationBlobStoreOption(cfg config.AutomationConfig, options []Options) (blob.Store, error) {
	if len(options) > 0 && options[0].AutomationBlobStore != nil {
		return options[0].AutomationBlobStore, nil
	}
	switch config.NormalizeAutomationBlobDriver(cfg.BlobDriver) {
	case config.AutomationBlobDriverFilesystem:
		store, err := blob.NewFilesystem(cfg.BlobFilesystemRoot)
		if err != nil {
			return nil, err
		}
		return store, nil
	default:
		return nil, fmt.Errorf("unsupported automation blob driver %q", cfg.BlobDriver)
	}
}

func automationServerRunnerOption(cfg config.AutomationConfig, store *automationstore.Store, blobStore blob.Store, checkoutMaterializer runtime.CheckoutMaterializer, wikiPublisher runtime.WikiPublisher, treePublisher runtime.TreePublisher, credentialKey []byte, options []Options) (*runtime.ServerRunner, error) {
	if !cfg.ServerRunnerEnabled || !cfg.HasRole(config.AutomationRoleRunnerDispatch) {
		return nil, nil
	}
	resolveExecutor := automationJobExecutorOption
	if len(options) > 0 && options[0].AutomationJobExecutorResolver != nil {
		resolveExecutor = options[0].AutomationJobExecutorResolver
	}
	executor, labels, err := resolveExecutor(cfg, options)
	logger := slog.Default().With(
		slog.String("domain", "automation"),
		slog.String("component", "server_runner"),
	)
	if err != nil {
		if errors.Is(err, runtime.ErrDockerUnavailable) {
			logger.Warn("automation server runner disabled", "reason", err)
			return nil, nil
		}
		return nil, err
	}
	runner, err := runtime.NewServerRunner(store, blobStore, runtime.ServerRunnerConfig{
		RunnerID:             cfg.ServerRunnerID,
		Labels:               labels,
		Concurrency:          cfg.ServerRunnerConcurrency,
		WorkDir:              cfg.ServerRunnerWorkDir,
		PollInterval:         cfg.ServerRunnerPollInterval,
		JobTimeout:           cfg.ServerRunnerJobTimeout,
		CheckoutMaterializer: checkoutMaterializer,
		WikiPublisher:        wikiPublisher,
		TreePublisher:        treePublisher,
		CheckoutTimeout:      cfg.CheckoutTimeout,
		CheckoutMaxBytes:     cfg.CheckoutMaxBytes,
		CheckoutMaxFiles:     cfg.CheckoutMaxFiles,
		MaxLogChunkBytes:     cfg.MaxLogChunkBytes,
		MaxRunnerLabels:      cfg.MaxRunnerLabels,
		Executor:             executor,
		CredentialKey:        credentialKey,
	}, nil, logger)
	if err != nil {
		if closer, ok := executor.(interface{ Close() error }); ok {
			_ = closer.Close()
		}
		return nil, err
	}
	return runner, nil
}

type automationJobExecutorResolver func(config.AutomationConfig, []Options) (runtime.JobExecutor, []string, error)

func automationCheckoutMaterializerOption(agsSrv agsServer) runtime.CheckoutMaterializer {
	archiver, ok := agsSrv.(agsRepositoryArchiver)
	if !ok {
		return nil
	}
	return automationservice.AGSAutomationCheckoutMaterializer{Archiver: agsAutomationRepositoryArchiver{archiver: archiver}}
}

func automationJobExecutorOption(cfg config.AutomationConfig, options []Options) (runtime.JobExecutor, []string, error) {
	if len(options) > 0 && options[0].AutomationJobExecutor != nil {
		return options[0].AutomationJobExecutor, cfg.ServerRunnerLabels, nil
	}
	switch config.NormalizeAutomationExecutor(cfg.Executor) {
	case config.AutomationExecutorDocker:
		executor, err := runtime.NewDockerExecutor(runtime.DockerExecutorConfig{
			Images:      cfg.DockerImages,
			NetworkMode: cfg.DockerNetwork,
			CPUs:        cfg.DockerCPUs,
			MemoryBytes: cfg.DockerMemoryBytes,
			PidsLimit:   cfg.DockerPidsLimit,
			User:        cfg.DockerUser,
			Mounts:      automationDockerMountsFromConfig(cfg.DockerMounts),
		})
		if err != nil {
			return nil, nil, err
		}
		return executor, executor.Labels(), nil
	default:
		return nil, nil, fmt.Errorf("unsupported automation executor %q", cfg.Executor)
	}
}

func automationDockerMountsFromConfig(mounts []config.AutomationDockerMount) []runtime.DockerBindMount {
	out := make([]runtime.DockerBindMount, 0, len(mounts))
	for _, item := range mounts {
		out = append(out, runtime.DockerBindMount{
			Source:   item.Source,
			Target:   item.Target,
			ReadOnly: item.ReadOnly,
		})
	}
	return out
}
