using ColaFlow.Modules.Mcp.Application.DTOs;
using ColaFlow.Modules.Mcp.Domain.Entities;
using ColaFlow.Modules.Mcp.Domain.Repositories;
using ColaFlow.Modules.Mcp.Domain.ValueObjects;
using Microsoft.Extensions.Logging;

namespace ColaFlow.Modules.Mcp.Application.Services;

/// <summary>
/// Service implementation for MCP API Key management
/// </summary>
public class McpApiKeyService : IMcpApiKeyService
{
    private readonly IMcpApiKeyRepository _repository;
    private readonly ILogger<McpApiKeyService> _logger;

    public McpApiKeyService(
        IMcpApiKeyRepository repository,
        ILogger<McpApiKeyService> logger)
    {
        _repository = repository ?? throw new ArgumentNullException(nameof(repository));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<CreateApiKeyResponse> CreateAsync(CreateApiKeyRequest request, CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("Creating API Key '{Name}' for Tenant {TenantId} User {UserId}",
            request.Name, request.TenantId, request.UserId);

        // Create permissions
        var permissions = ApiKeyPermissions.Custom(
            request.Read,
            request.Write,
            request.AllowedResources,
            request.AllowedTools);

        // Create API Key entity
        var (apiKey, plainKey) = McpApiKey.Create(
            request.Name,
            request.TenantId,
            request.UserId,
            permissions,
            request.ExpirationDays,
            request.IpWhitelist);

        // Add description if provided
        if (!string.IsNullOrWhiteSpace(request.Description))
        {
            apiKey.UpdateMetadata(description: request.Description);
        }

        // Save to database
        await _repository.AddAsync(apiKey, cancellationToken);

        _logger.LogInformation("API Key created successfully: {ApiKeyId}", apiKey.Id);

        return new CreateApiKeyResponse
        {
            Id = apiKey.Id,
            Name = apiKey.Name,
            PlainKey = plainKey, // IMPORTANT: Only returned once!
            KeyPrefix = apiKey.KeyPrefix,
            ExpiresAt = apiKey.ExpiresAt,
            Permissions = new ApiKeyPermissionsDto
            {
                Read = apiKey.Permissions.Read,
                Write = apiKey.Permissions.Write,
                AllowedResources = apiKey.Permissions.AllowedResources,
                AllowedTools = apiKey.Permissions.AllowedTools
            }
        };
    }

    public async Task<ApiKeyValidationResult> ValidateAsync(string plainKey, string? ipAddress = null, CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(plainKey))
        {
            return ApiKeyValidationResult.Invalid("API Key is empty");
        }

        // Extract prefix for fast lookup
        if (plainKey.Length < 12)
        {
            return ApiKeyValidationResult.Invalid("Invalid API Key format");
        }

        var keyPrefix = plainKey.Substring(0, 12);

        // Lookup by prefix
        var apiKey = await _repository.GetByPrefixAsync(keyPrefix, cancellationToken);
        if (apiKey == null)
        {
            _logger.LogWarning("API Key not found for prefix: {KeyPrefix}", keyPrefix);
            return ApiKeyValidationResult.Invalid("Invalid API Key");
        }

        // Verify hash
        if (!apiKey.VerifyKey(plainKey))
        {
            _logger.LogWarning("API Key hash verification failed for {ApiKeyId}", apiKey.Id);
            return ApiKeyValidationResult.Invalid("Invalid API Key");
        }

        // Check status
        if (apiKey.Status != ApiKeyStatus.Active)
        {
            _logger.LogWarning("API Key {ApiKeyId} is revoked", apiKey.Id);
            return ApiKeyValidationResult.Invalid("API Key has been revoked");
        }

        // Check expiration
        if (apiKey.IsExpired())
        {
            _logger.LogWarning("API Key {ApiKeyId} is expired", apiKey.Id);
            return ApiKeyValidationResult.Invalid("API Key has expired");
        }

        // Check IP whitelist
        if (!string.IsNullOrWhiteSpace(ipAddress) && !apiKey.IsIpAllowed(ipAddress))
        {
            _logger.LogWarning("API Key {ApiKeyId} rejected - IP {IpAddress} not whitelisted", apiKey.Id, ipAddress);
            return ApiKeyValidationResult.Invalid("IP address not allowed");
        }

        // Record usage (async, don't block)
        try
        {
            apiKey.RecordUsage();
            await _repository.UpdateAsync(apiKey, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to record API Key usage for {ApiKeyId}", apiKey.Id);
            // Continue - don't block auth for usage tracking failures
        }

        _logger.LogInformation("API Key {ApiKeyId} validated successfully for Tenant {TenantId}",
            apiKey.Id, apiKey.TenantId);

        return ApiKeyValidationResult.Valid(
            apiKey.Id,
            apiKey.TenantId,
            apiKey.UserId,
            apiKey.Permissions);
    }

    public async Task<ApiKeyResponse?> GetByIdAsync(Guid id, Guid tenantId, CancellationToken cancellationToken = default)
    {
        var apiKey = await _repository.GetByIdAsync(id, cancellationToken);
        if (apiKey == null || apiKey.TenantId != tenantId)
        {
            return null;
        }

        return MapToResponse(apiKey);
    }

    public async Task<List<ApiKeyResponse>> GetByTenantIdAsync(Guid tenantId, CancellationToken cancellationToken = default)
    {
        var apiKeys = await _repository.GetByTenantIdAsync(tenantId, cancellationToken);
        return apiKeys.Select(MapToResponse).ToList();
    }

    public async Task<List<ApiKeyResponse>> GetByUserIdAsync(Guid userId, Guid tenantId, CancellationToken cancellationToken = default)
    {
        var apiKeys = await _repository.GetByUserIdAsync(userId, tenantId, cancellationToken);
        return apiKeys.Select(MapToResponse).ToList();
    }

    public async Task<ApiKeyResponse> UpdateMetadataAsync(Guid id, Guid tenantId, UpdateApiKeyMetadataRequest request, CancellationToken cancellationToken = default)
    {
        var apiKey = await _repository.GetByIdAsync(id, cancellationToken);
        if (apiKey == null || apiKey.TenantId != tenantId)
        {
            throw new InvalidOperationException("API Key not found");
        }

        apiKey.UpdateMetadata(request.Name, request.Description);
        await _repository.UpdateAsync(apiKey, cancellationToken);

        _logger.LogInformation("API Key {ApiKeyId} metadata updated", apiKey.Id);

        return MapToResponse(apiKey);
    }

    public async Task<ApiKeyResponse> UpdatePermissionsAsync(Guid id, Guid tenantId, UpdateApiKeyPermissionsRequest request, CancellationToken cancellationToken = default)
    {
        var apiKey = await _repository.GetByIdAsync(id, cancellationToken);
        if (apiKey == null || apiKey.TenantId != tenantId)
        {
            throw new InvalidOperationException("API Key not found");
        }

        var permissions = ApiKeyPermissions.Custom(
            request.Read,
            request.Write,
            request.AllowedResources,
            request.AllowedTools);

        apiKey.UpdatePermissions(permissions);

        if (request.IpWhitelist != null)
        {
            apiKey.UpdateIpWhitelist(request.IpWhitelist);
        }

        await _repository.UpdateAsync(apiKey, cancellationToken);

        _logger.LogInformation("API Key {ApiKeyId} permissions updated", apiKey.Id);

        return MapToResponse(apiKey);
    }

    public async Task RevokeAsync(Guid id, Guid tenantId, Guid revokedBy, CancellationToken cancellationToken = default)
    {
        var apiKey = await _repository.GetByIdAsync(id, cancellationToken);
        if (apiKey == null || apiKey.TenantId != tenantId)
        {
            throw new InvalidOperationException("API Key not found");
        }

        apiKey.Revoke(revokedBy);
        await _repository.UpdateAsync(apiKey, cancellationToken);

        _logger.LogInformation("API Key {ApiKeyId} revoked by {RevokedBy}", apiKey.Id, revokedBy);
    }

    private static ApiKeyResponse MapToResponse(McpApiKey apiKey)
    {
        return new ApiKeyResponse
        {
            Id = apiKey.Id,
            TenantId = apiKey.TenantId,
            UserId = apiKey.UserId,
            Name = apiKey.Name,
            Description = apiKey.Description,
            KeyPrefix = apiKey.KeyPrefix,
            Status = apiKey.Status.ToString(),
            Permissions = new ApiKeyPermissionsDto
            {
                Read = apiKey.Permissions.Read,
                Write = apiKey.Permissions.Write,
                AllowedResources = apiKey.Permissions.AllowedResources,
                AllowedTools = apiKey.Permissions.AllowedTools
            },
            IpWhitelist = apiKey.IpWhitelist,
            LastUsedAt = apiKey.LastUsedAt,
            UsageCount = apiKey.UsageCount,
            CreatedAt = apiKey.CreatedAt,
            ExpiresAt = apiKey.ExpiresAt,
            RevokedAt = apiKey.RevokedAt,
            RevokedBy = apiKey.RevokedBy
        };
    }
}
