package llm

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"gitea.maximumdirect.net/eric/promptkit"
)

type PromptKitProfileSourceConfig struct {
	ProfileDir   string
	ProfileFile  string
	LocalBackend *PromptKitLocalBackendConfig
}

type PromptKitProfileInspectorConfig struct {
	Source PromptKitProfileSourceConfig
	Assets *AssetRegistry
}

func (c PromptKitProfileSourceConfig) localEndpoint() string {
	if c.LocalBackend == nil {
		return ""
	}
	return c.LocalBackend.Endpoint
}

type PromptKitProfileInspector struct {
	engine *promptkit.Engine
}

type PromptKitProfileInspection struct {
	ProfileID             string
	BackendID             string
	Model                 string
	CredentialEnvironment string
	CredentialRequired    bool
}

type PromptKitProfileInspectionError struct {
	ProfileID string
	err       error
}

func (e *PromptKitProfileInspectionError) Error() string {
	switch {
	case errors.Is(e.err, promptkit.ErrProfileNotFound):
		return fmt.Sprintf("PromptKit profile %q is not configured", e.ProfileID)
	case errors.Is(e.err, promptkit.ErrInvalidRequest):
		return fmt.Sprintf("PromptKit profile ID %q is invalid", e.ProfileID)
	case errors.Is(e.err, promptkit.ErrProfileLoad):
		return fmt.Sprintf("PromptKit profile %q is invalid or unreadable", e.ProfileID)
	default:
		return fmt.Sprintf("PromptKit profile %q could not be inspected", e.ProfileID)
	}
}

func (e *PromptKitProfileInspectionError) Unwrap() error {
	return e.err
}

type promptKitProfileConfigurationError struct {
	err error
}

func (e *promptKitProfileConfigurationError) Error() string {
	return "PromptKit profile configuration is invalid or unreadable"
}

func (e *promptKitProfileConfigurationError) Unwrap() error {
	return e.err
}

func NewPromptKitProfileInspector(cfg PromptKitProfileInspectorConfig) (*PromptKitProfileInspector, error) {
	source, options, err := promptKitProfileSourceEngineOptions(cfg.Source)
	if err != nil {
		return nil, err
	}
	if cfg.Assets != nil {
		fallbackOption, hasFallback, err := cfg.Assets.promptKitFallbackProfileOption()
		if err != nil {
			return nil, err
		}
		if hasFallback {
			options = append(options, fallbackOption)
		}
	}
	engine, err := promptkit.NewEngine(promptkit.Config{
		PromptDir:  ".",
		ProfileDir: source.ProfileDir,
	}, options...)
	if err != nil {
		return nil, &promptKitProfileConfigurationError{err: err}
	}
	return &PromptKitProfileInspector{engine: engine}, nil
}

func (i *PromptKitProfileInspector) InspectProfile(ctx context.Context, profileID string) (PromptKitProfileInspection, error) {
	if i == nil || i.engine == nil {
		return PromptKitProfileInspection{}, fmt.Errorf("PromptKit profile inspector must not be nil")
	}
	profileID = strings.TrimSpace(profileID)
	inspection, err := i.engine.InspectProfile(ctx, profileID)
	if err != nil {
		if ctxErr := ctx.Err(); ctxErr != nil {
			return PromptKitProfileInspection{}, ctxErr
		}
		return PromptKitProfileInspection{}, &PromptKitProfileInspectionError{
			ProfileID: profileID,
			err:       err,
		}
	}
	return PromptKitProfileInspection{
		ProfileID:             inspection.ProfileID,
		BackendID:             strings.TrimSpace(inspection.EffectiveModelParams.BackendID),
		Model:                 strings.TrimSpace(inspection.EffectiveModelParams.Model),
		CredentialEnvironment: strings.TrimSpace(inspection.EffectiveModelParams.APIKeyEnv),
		CredentialRequired:    inspection.APIKeyRequired,
	}, nil
}

func promptKitProfileSourceEngineOptions(cfg PromptKitProfileSourceConfig) (PromptKitProfileSourceConfig, []promptkit.Option, error) {
	source := PromptKitProfileSourceConfig{
		ProfileDir:  strings.TrimSpace(cfg.ProfileDir),
		ProfileFile: strings.TrimSpace(cfg.ProfileFile),
	}
	if source.ProfileDir != "" && source.ProfileFile != "" {
		return PromptKitProfileSourceConfig{}, nil, fmt.Errorf("PromptKit profile_dir and profile_file are mutually exclusive")
	}
	if cfg.LocalBackend != nil {
		localBackend := *cfg.LocalBackend
		localBackend.Endpoint = strings.TrimSpace(localBackend.Endpoint)
		source.LocalBackend = &localBackend
	}

	var options []promptkit.Option
	if source.ProfileFile != "" {
		options = append(options, promptkit.WithProfileFile(source.ProfileFile))
	}
	if source.LocalBackend != nil {
		options = append(options, PromptKitLocalBackendOption(*source.LocalBackend))
	}
	return source, options, nil
}
