using System.Security.Cryptography;
using ColaFlow.Shared.Kernel.Common;
using ColaFlow.Modules.Mcp.Domain.Events;
using ColaFlow.Modules.Mcp.Domain.ValueObjects;

namespace ColaFlow.Modules.Mcp.Domain.Entities;

/// <summary>
/// MCP API Key aggregate root - manages API keys for AI agent authentication
/// </summary>
public sealed class McpApiKey : AggregateRoot
{
    // Multi-tenant isolation
    public Guid TenantId { get; private set; }
    public Guid UserId { get; private set; }

    // Security fields
    public string KeyHash { get; private set; } = null!;
    public string KeyPrefix { get; private set; } = null!;

    // Metadata
    public string Name { get; private set; } = null!;
    public string? Description { get; private set; }

    // Permissions
    public ApiKeyPermissions Permissions { get; private set; } = null!;
    public List<string>? IpWhitelist { get; private set; }

    // Status tracking
    public ApiKeyStatus Status { get; private set; }
    public DateTime? LastUsedAt { get; private set; }
    public long UsageCount { get; private set; }

    // Lifecycle
    public DateTime CreatedAt { get; private set; }
    public DateTime ExpiresAt { get; private set; }
    public DateTime? RevokedAt { get; private set; }
    public Guid? RevokedBy { get; private set; }

    // Private constructor for EF Core
    private McpApiKey() : base()
    {
    }

    /// <summary>
    /// Factory method to create a new API Key
    /// </summary>
    /// <param name="name">Friendly name for the API key</param>
    /// <param name="tenantId">Tenant ID for multi-tenant isolation</param>
    /// <param name="userId">User ID who created the key</param>
    /// <param name="permissions">Permission configuration</param>
    /// <param name="expirationDays">Number of days until expiration (default: 90)</param>
    /// <param name="ipWhitelist">Optional list of allowed IP addresses</param>
    /// <returns>Tuple of (apiKey entity, plaintext key) - plaintext key shown only once!</returns>
    public static (McpApiKey ApiKey, string PlainKey) Create(
        string name,
        Guid tenantId,
        Guid userId,
        ApiKeyPermissions permissions,
        int expirationDays = 90,
        List<string>? ipWhitelist = null)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException("API Key name cannot be empty", nameof(name));

        if (tenantId == Guid.Empty)
            throw new ArgumentException("Tenant ID cannot be empty", nameof(tenantId));

        if (userId == Guid.Empty)
            throw new ArgumentException("User ID cannot be empty", nameof(userId));

        if (permissions == null)
            throw new ArgumentNullException(nameof(permissions));

        if (expirationDays <= 0 || expirationDays > 365)
            throw new ArgumentException("Expiration days must be between 1 and 365", nameof(expirationDays));

        // Generate cryptographically secure API key
        var plainKey = GenerateApiKey();
        var keyHash = BCrypt.Net.BCrypt.HashPassword(plainKey, workFactor: 12);
        var keyPrefix = plainKey.Substring(0, 12); // "cola_abc123..."

        var apiKey = new McpApiKey
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            UserId = userId,
            KeyHash = keyHash,
            KeyPrefix = keyPrefix,
            Name = name,
            Permissions = permissions,
            Status = ApiKeyStatus.Active,
            CreatedAt = DateTime.UtcNow,
            ExpiresAt = DateTime.UtcNow.AddDays(expirationDays),
            UsageCount = 0,
            IpWhitelist = ipWhitelist
        };

        apiKey.AddDomainEvent(new ApiKeyCreatedEvent(apiKey.Id, apiKey.Name, apiKey.TenantId, apiKey.UserId));

        return (apiKey, plainKey);
    }

    /// <summary>
    /// Revoke the API key (soft delete)
    /// </summary>
    /// <param name="revokedBy">User ID who revoked the key</param>
    public void Revoke(Guid revokedBy)
    {
        if (Status == ApiKeyStatus.Revoked)
            throw new InvalidOperationException("API Key is already revoked");

        if (revokedBy == Guid.Empty)
            throw new ArgumentException("Revoked by user ID cannot be empty", nameof(revokedBy));

        Status = ApiKeyStatus.Revoked;
        RevokedAt = DateTime.UtcNow;
        RevokedBy = revokedBy;

        AddDomainEvent(new ApiKeyRevokedEvent(Id, Name, TenantId, revokedBy));
    }

    /// <summary>
    /// Record successful usage of the API key
    /// </summary>
    public void RecordUsage()
    {
        if (Status != ApiKeyStatus.Active)
            throw new InvalidOperationException("Cannot record usage for inactive API key");

        if (IsExpired())
            throw new InvalidOperationException("Cannot record usage for expired API key");

        LastUsedAt = DateTime.UtcNow;
        UsageCount++;
    }

    /// <summary>
    /// Update API key metadata
    /// </summary>
    public void UpdateMetadata(string? name = null, string? description = null)
    {
        if (Status == ApiKeyStatus.Revoked)
            throw new InvalidOperationException("Cannot update revoked API key");

        if (!string.IsNullOrWhiteSpace(name))
            Name = name;

        Description = description;
    }

    /// <summary>
    /// Update API key permissions
    /// </summary>
    public void UpdatePermissions(ApiKeyPermissions permissions)
    {
        if (Status == ApiKeyStatus.Revoked)
            throw new InvalidOperationException("Cannot update permissions for revoked API key");

        if (permissions == null)
            throw new ArgumentNullException(nameof(permissions));

        Permissions = permissions;
    }

    /// <summary>
    /// Update IP whitelist
    /// </summary>
    public void UpdateIpWhitelist(List<string>? ipWhitelist)
    {
        if (Status == ApiKeyStatus.Revoked)
            throw new InvalidOperationException("Cannot update IP whitelist for revoked API key");

        IpWhitelist = ipWhitelist;
    }

    /// <summary>
    /// Check if the API key is expired
    /// </summary>
    public bool IsExpired()
    {
        return DateTime.UtcNow > ExpiresAt;
    }

    /// <summary>
    /// Check if the API key is valid for use
    /// </summary>
    public bool IsValid()
    {
        return Status == ApiKeyStatus.Active && !IsExpired();
    }

    /// <summary>
    /// Verify the provided plain key against the stored hash
    /// </summary>
    public bool VerifyKey(string plainKey)
    {
        if (string.IsNullOrWhiteSpace(plainKey))
            return false;

        try
        {
            return BCrypt.Net.BCrypt.Verify(plainKey, KeyHash);
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// Check if the provided IP address is whitelisted
    /// </summary>
    public bool IsIpAllowed(string ipAddress)
    {
        // If no whitelist configured, allow all IPs
        if (IpWhitelist == null || IpWhitelist.Count == 0)
            return true;

        return IpWhitelist.Contains(ipAddress);
    }

    /// <summary>
    /// Generate a cryptographically secure API key
    /// Format: cola_<36 random characters>
    /// </summary>
    private static string GenerateApiKey()
    {
        const int byteLength = 30; // Generates 40+ chars in base64
        var bytes = new byte[byteLength];

        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(bytes);

        var base64 = Convert.ToBase64String(bytes)
            .Replace("+", "")
            .Replace("/", "")
            .Replace("=", "");

        // Take first 36 characters
        var randomPart = base64.Length >= 36 ? base64.Substring(0, 36) : base64.PadRight(36, '0');

        return $"cola_{randomPart}";
    }
}
