package cli

import (
	"context"
	"fmt"

	"gitea.maximumdirect.net/eric/notarius/internal/core/artifacts"
	"gitea.maximumdirect.net/eric/notarius/internal/core/config"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
	dndregister "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/register"
	genericregister "gitea.maximumdirect.net/eric/notarius/internal/modules/generic/register"
	seriatimregister "gitea.maximumdirect.net/eric/notarius/internal/modules/seriatim/register"
)

type productionComponents struct {
	registries pipeline.Registries
	assets     *llm.AssetRegistry
}

func newProductionComponents() (productionComponents, error) {
	registries := pipeline.Registries{
		Inputs:           pipeline.NewInputAdapterRegistry(),
		Chunkers:         pipeline.NewChunkerRegistry(),
		ArtifactCodecs:   pipeline.NewArtifactCodecRegistry(),
		ArtifactEvidence: pipeline.NewArtifactEvidenceRegistry(),
		Extractors:       pipeline.NewExtractorRegistry(),
		Mergers:          pipeline.NewMergerRegistry(),
		Normalizers:      pipeline.NewNormalizerRegistry(),
		Validators:       pipeline.NewValidatorRegistry(),
		ValidatorChains:  pipeline.NewValidatorChainRegistry(),
		Outputs:          pipeline.NewOutputEncoderRegistry(),
	}
	assets := llm.NewAssetRegistry()
	registrars := []struct {
		name     string
		register func(pipeline.Registries, *llm.AssetRegistry) error
	}{
		{name: "generic", register: genericregister.Register},
		{name: "seriatim", register: seriatimregister.Register},
		{name: "dnd", register: dndregister.Register},
	}
	for _, registrar := range registrars {
		if err := registrar.register(registries, assets); err != nil {
			return productionComponents{}, fmt.Errorf("register %s module family: %w", registrar.name, err)
		}
	}
	return productionComponents{registries: registries, assets: assets}, nil
}

func productionRegistries() (pipeline.Registries, error) {
	components, err := newProductionComponents()
	return components.registries, err
}

func productionCatalog() (pipeline.ModuleCatalog, error) {
	registries, err := productionRegistries()
	if err != nil {
		return pipeline.ModuleCatalog{}, err
	}
	return catalogFromRegistries(registries), nil
}

func productionPromptAssets() (*llm.AssetRegistry, error) {
	components, err := newProductionComponents()
	return components.assets, err
}

func effectiveCatalog(opts Options) (pipeline.ModuleCatalog, error) {
	if !isEmptyCatalog(opts.Catalog) {
		return opts.Catalog, nil
	}
	if !isEmptyRegistries(opts.Registries) {
		return catalogFromRegistries(opts.Registries), nil
	}
	return productionCatalog()
}

func effectiveRegistries(opts Options) (pipeline.Registries, error) {
	if !isEmptyRegistries(opts.Registries) {
		return opts.Registries, nil
	}
	if !isEmptyCatalog(opts.Catalog) {
		return registriesFromCatalog(opts.Catalog), nil
	}
	return productionRegistries()
}

func catalogFromRegistries(registries pipeline.Registries) pipeline.ModuleCatalog {
	return pipeline.ModuleCatalog{
		Inputs:           registries.Inputs,
		Chunkers:         registries.Chunkers,
		ArtifactCodecs:   registries.ArtifactCodecs,
		ArtifactEvidence: registries.ArtifactEvidence,
		Extractors:       registries.Extractors,
		Mergers:          registries.Mergers,
		Normalizers:      registries.Normalizers,
		Validators:       registries.Validators,
		ValidatorChains:  registries.ValidatorChains,
		Outputs:          registries.Outputs,
	}
}

func registriesFromCatalog(catalog pipeline.ModuleCatalog) pipeline.Registries {
	return pipeline.Registries{
		Inputs:           catalog.Inputs,
		Chunkers:         catalog.Chunkers,
		ArtifactCodecs:   catalog.ArtifactCodecs,
		ArtifactEvidence: catalog.ArtifactEvidence,
		Extractors:       catalog.Extractors,
		Mergers:          catalog.Mergers,
		Normalizers:      catalog.Normalizers,
		Validators:       catalog.Validators,
		ValidatorChains:  catalog.ValidatorChains,
		Outputs:          catalog.Outputs,
	}
}

func isEmptyCatalog(catalog pipeline.ModuleCatalog) bool {
	return catalog.Inputs == nil &&
		catalog.Chunkers == nil &&
		catalog.ArtifactCodecs == nil &&
		catalog.ArtifactEvidence == nil &&
		catalog.Extractors == nil &&
		catalog.Mergers == nil &&
		catalog.Normalizers == nil &&
		catalog.Validators == nil &&
		catalog.ValidatorChains == nil &&
		catalog.Outputs == nil
}

func isEmptyRegistries(registries pipeline.Registries) bool {
	return registries.Inputs == nil &&
		registries.Chunkers == nil &&
		registries.ArtifactCodecs == nil &&
		registries.ArtifactEvidence == nil &&
		registries.Extractors == nil &&
		registries.Mergers == nil &&
		registries.Normalizers == nil &&
		registries.Validators == nil &&
		registries.ValidatorChains == nil &&
		registries.Outputs == nil
}

func productionLLMClientFactoryWithAssets(assets *llm.AssetRegistry) LLMClientFactory {
	return func(ctx context.Context, cfg config.Config, profileID string, overrides LLMRuntimeOverrides) (contracts.StructuredLLMClient, []artifacts.LLMProfileManifest, error) {
		return buildProductionLLMClient(ctx, cfg, profileID, overrides, assets)
	}
}

func buildProductionLLMClient(ctx context.Context, cfg config.Config, profileID string, overrides LLMRuntimeOverrides, assets *llm.AssetRegistry) (contracts.StructuredLLMClient, []artifacts.LLMProfileManifest, error) {
	if err := ctx.Err(); err != nil {
		return nil, nil, err
	}
	if assets == nil {
		return nil, nil, fmt.Errorf("production asset registry must not be nil")
	}
	recorder := llm.NewLLMProfileRecorder()
	client, err := llm.NewPromptKitClient(llm.PromptKitClientConfig{
		ProfileDir:      cfg.PromptKit.ProfileDir,
		ProfileFile:     cfg.PromptKit.ProfileFile,
		LocalBackend:    mapPromptKitLocalBackend(cfg.PromptKit.LocalBackend),
		Assets:          assets,
		Recorder:        recorder,
		ReasoningEffort: overrides.ReasoningEffort,
	})
	if err != nil {
		return nil, nil, fmt.Errorf("create PromptKit-backed LLM client: %w", err)
	}
	scheduler, err := llm.NewScheduler(cfg.Concurrency.TotalLLM)
	if err != nil {
		return nil, nil, fmt.Errorf("create LLM scheduler: %w", err)
	}
	return llm.NewScheduledClient(client, scheduler), nil, nil
}
