using Codex.Dal;
using Codex.Dal.Entities;
using Codex.Dal.Enums;
using Codex.Dal.Services;
using FluentValidation;
using OpenHarbor.CQRS.Abstractions;

namespace Codex.CQRS.Commands;

/// <summary>
/// Command to create a new AI agent with configuration
/// </summary>
public record CreateAgentCommand
{
    /// <summary>
    /// Display name of the agent
    /// </summary>
    public string Name { get; init; } = string.Empty;

    /// <summary>
    /// Description of the agent's purpose and capabilities
    /// </summary>
    public string Description { get; init; } = string.Empty;

    /// <summary>
    /// Type of agent (CodeGenerator, CodeReviewer, etc.)
    /// </summary>
    public AgentType Type { get; init; }

    /// <summary>
    /// Model provider name (e.g., "openai", "anthropic", "ollama")
    /// </summary>
    public string ModelProvider { get; init; } = string.Empty;

    /// <summary>
    /// Specific model name (e.g., "gpt-4o", "claude-3.5-sonnet", "codellama:7b")
    /// </summary>
    public string ModelName { get; init; } = string.Empty;

    /// <summary>
    /// Type of provider (CloudApi, LocalEndpoint, Custom)
    /// </summary>
    public ModelProviderType ProviderType { get; init; }

    /// <summary>
    /// Model endpoint URL (required for LocalEndpoint, optional for CloudApi)
    /// </summary>
    public string? ModelEndpoint { get; init; }

    /// <summary>
    /// API key for cloud providers (will be encrypted). Not required for local endpoints.
    /// </summary>
    public string? ApiKey { get; init; }

    /// <summary>
    /// Temperature parameter for model generation (0.0 to 2.0, default: 0.7)
    /// </summary>
    public double Temperature { get; init; } = 0.7;

    /// <summary>
    /// Maximum tokens to generate in response (default: 4000)
    /// </summary>
    public int MaxTokens { get; init; } = 4000;

    /// <summary>
    /// System prompt defining agent behavior and instructions
    /// </summary>
    public string SystemPrompt { get; init; } = string.Empty;

    /// <summary>
    /// Whether conversation memory is enabled for this agent (default: true)
    /// </summary>
    public bool EnableMemory { get; init; } = true;

    /// <summary>
    /// Number of recent messages to include in context (default: 10, range: 1-100)
    /// </summary>
    public int ConversationWindowSize { get; init; } = 10;
}

/// <summary>
/// Handler for creating a new agent
/// </summary>
public class CreateAgentCommandHandler(CodexDbContext dbContext, IEncryptionService encryptionService)
    : ICommandHandler<CreateAgentCommand>
{
    public async Task HandleAsync(CreateAgentCommand command, CancellationToken cancellationToken = default)
    {
        var agent = new Agent
        {
            Id = Guid.NewGuid(),
            Name = command.Name,
            Description = command.Description,
            Type = command.Type,
            ModelProvider = command.ModelProvider.ToLowerInvariant(),
            ModelName = command.ModelName,
            ProviderType = command.ProviderType,
            ModelEndpoint = command.ModelEndpoint,
            ApiKeyEncrypted = command.ApiKey != null ? encryptionService.Encrypt(command.ApiKey) : null,
            Temperature = command.Temperature,
            MaxTokens = command.MaxTokens,
            SystemPrompt = command.SystemPrompt,
            EnableMemory = command.EnableMemory,
            ConversationWindowSize = command.ConversationWindowSize,
            Status = AgentStatus.Active,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow,
            IsDeleted = false
        };

        dbContext.Agents.Add(agent);
        await dbContext.SaveChangesAsync(cancellationToken);
    }
}

/// <summary>
/// Validator for CreateAgentCommand
/// </summary>
public class CreateAgentCommandValidator : AbstractValidator<CreateAgentCommand>
{
    public CreateAgentCommandValidator()
    {
        RuleFor(x => x.Name)
            .NotEmpty().WithMessage("Agent name is required")
            .MaximumLength(200).WithMessage("Agent name must not exceed 200 characters");

        RuleFor(x => x.Description)
            .NotEmpty().WithMessage("Agent description is required")
            .MaximumLength(1000).WithMessage("Agent description must not exceed 1000 characters");

        RuleFor(x => x.ModelProvider)
            .NotEmpty().WithMessage("Model provider is required")
            .MaximumLength(100).WithMessage("Model provider must not exceed 100 characters")
            .Must(provider => new[] { "openai", "anthropic", "ollama" }.Contains(provider.ToLowerInvariant()))
            .WithMessage("Model provider must be one of: openai, anthropic, ollama");

        RuleFor(x => x.ModelName)
            .NotEmpty().WithMessage("Model name is required")
            .MaximumLength(100).WithMessage("Model name must not exceed 100 characters");

        RuleFor(x => x.SystemPrompt)
            .NotEmpty().WithMessage("System prompt is required")
            .MinimumLength(10).WithMessage("System prompt must be at least 10 characters");

        RuleFor(x => x.Temperature)
            .InclusiveBetween(0.0, 2.0).WithMessage("Temperature must be between 0.0 and 2.0");

        RuleFor(x => x.MaxTokens)
            .GreaterThan(0).WithMessage("Max tokens must be greater than 0")
            .LessThanOrEqualTo(100000).WithMessage("Max tokens must not exceed 100,000");

        RuleFor(x => x.ConversationWindowSize)
            .InclusiveBetween(1, 100).WithMessage("Conversation window size must be between 1 and 100");

        // Cloud API providers require an API key
        RuleFor(x => x.ApiKey)
            .NotEmpty()
            .When(x => x.ProviderType == ModelProviderType.CloudApi)
            .WithMessage("API key is required for cloud API providers");

        // Local endpoints require a valid URL
        RuleFor(x => x.ModelEndpoint)
            .NotEmpty()
            .When(x => x.ProviderType == ModelProviderType.LocalEndpoint)
            .WithMessage("Model endpoint URL is required for local endpoints");

        RuleFor(x => x.ModelEndpoint)
            .Must(BeAValidUrl!)
            .When(x => !string.IsNullOrWhiteSpace(x.ModelEndpoint))
            .WithMessage("Model endpoint must be a valid URL");
    }

    private static bool BeAValidUrl(string url)
    {
        return Uri.TryCreate(url, UriKind.Absolute, out var uriResult)
               && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
    }
}
