using Fengling.Console.Models.Dtos;
using OpenIddict.Abstractions;
using System.Security.Cryptography;
using System.Text.Json;

namespace Fengling.Console.Services;

public interface IOAuthClientService
{
    Task<(IEnumerable<OAuthClientDto> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null);
    Task<OAuthClientDto?> GetClientAsync(string id);
    Task<OAuthClientDto> CreateClientAsync(CreateClientDto dto);
    Task<object> GenerateSecretAsync(string id);
    Task<object> UpdateClientAsync(string id, UpdateClientDto dto);
    Task DeleteClientAsync(string id);
    object GetClientOptions();
}

public class OAuthClientService : IOAuthClientService
{
    private readonly IOpenIddictApplicationManager _applicationManager;
    private readonly HttpClient _httpClient;
    private readonly IConfiguration _configuration;
    private readonly ILogger<OAuthClientService> _logger;

    public OAuthClientService(
        IOpenIddictApplicationManager applicationManager,
        HttpClient httpClient,
        IConfiguration configuration,
        ILogger<OAuthClientService> logger)
    {
        _applicationManager = applicationManager;
        _httpClient = httpClient;
        _configuration = configuration;
        _logger = logger;
    }

    public async Task<(IEnumerable<OAuthClientDto> Items, int TotalCount)> GetClientsAsync(int page, int pageSize, string? displayName = null, string? clientId = null, string? status = null)
    {
        var applications = _applicationManager.ListAsync();
        var clientList = new List<OAuthClientDto>();

        await foreach (var application in applications)
        {
            var clientIdValue = await _applicationManager.GetClientIdAsync(application);
            var displayNameValue = await _applicationManager.GetDisplayNameAsync(application);

            if (!string.IsNullOrEmpty(displayName) && (string.IsNullOrEmpty(displayNameValue) || !displayNameValue.Contains(displayName, StringComparison.OrdinalIgnoreCase)))
                continue;

            if (!string.IsNullOrEmpty(clientId) && (string.IsNullOrEmpty(clientIdValue) || !clientIdValue.Contains(clientId, StringComparison.OrdinalIgnoreCase)))
                continue;

            var clientType = await _applicationManager.GetClientTypeAsync(application);
            var consentType = await _applicationManager.GetConsentTypeAsync(application);
            var permissions = await _applicationManager.GetPermissionsAsync(application);
            var redirectUris = await _applicationManager.GetRedirectUrisAsync(application);
            var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application);
            var applicationId = await _applicationManager.GetIdAsync(application);

            clientList.Add(new OAuthClientDto
            {
                Id = applicationId ?? clientIdValue ?? string.Empty,
                ClientId = clientIdValue ?? string.Empty,
                DisplayName = displayNameValue ?? string.Empty,
                RedirectUris = redirectUris.ToArray(),
                PostLogoutRedirectUris = postLogoutRedirectUris.ToArray(),
                Scopes = permissions
                    .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
                    .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length))
                    .ToArray(),
                GrantTypes = permissions
                    .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
                    .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length))
                    .ToArray(),
                ClientType = clientType?.ToString(),
                ConsentType = consentType?.ToString(),
                Status = "active"
            });
        }

        var sortedClients = clientList
            .OrderByDescending(c => c.ClientId)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToList();

        return (sortedClients, clientList.Count);
    }

    public async Task<OAuthClientDto?> GetClientAsync(string id)
    {
        var application = await _applicationManager.FindByIdAsync(id);
        if (application == null)
        {
            return null;
        }

        var clientIdValue = await _applicationManager.GetClientIdAsync(application);
        var displayNameValue = await _applicationManager.GetDisplayNameAsync(application);
        var clientType = await _applicationManager.GetClientTypeAsync(application);
        var consentType = await _applicationManager.GetConsentTypeAsync(application);
        var permissions = await _applicationManager.GetPermissionsAsync(application);
        var redirectUris = await _applicationManager.GetRedirectUrisAsync(application);
        var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application);

        return new OAuthClientDto
        {
            Id = id,
            ClientId = clientIdValue ?? string.Empty,
            DisplayName = displayNameValue ?? string.Empty,
            RedirectUris = redirectUris.ToArray(),
            PostLogoutRedirectUris = postLogoutRedirectUris.ToArray(),
            Scopes = permissions
                .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
                .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length))
                .ToArray(),
            GrantTypes = permissions
                .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
                .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length))
                .ToArray(),
            ClientType = clientType?.ToString(),
            ConsentType = consentType?.ToString(),
            Status = "active"
        };
    }

    public async Task<OAuthClientDto> CreateClientAsync(CreateClientDto dto)
    {
        var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
        var token = await GetAuthTokenAsync();

        var clientSecret = string.IsNullOrEmpty(dto.ClientSecret) ? GenerateSecureSecret() : dto.ClientSecret;

        var requestBody = new
        {
            dto.ClientId,
            ClientSecret = clientSecret,
            dto.DisplayName,
            dto.RedirectUris,
            dto.PostLogoutRedirectUris,
            dto.Scopes,
            dto.GrantTypes,
            dto.ClientType,
            dto.ConsentType,
            dto.Status,
            dto.Description
        };

        var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/api/OAuthClients")
        {
            Content = new StringContent(
                System.Text.Json.JsonSerializer.Serialize(requestBody),
                System.Text.Encoding.UTF8,
                "application/json")
        };
        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();
        var result = System.Text.Json.JsonSerializer.Deserialize<OAuthClientDto>(content);

        _logger.LogInformation("Created OAuth client {ClientId}", dto.ClientId);

        return result!;
    }

    public async Task<object> GenerateSecretAsync(string id)
    {
        var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
        var token = await GetAuthTokenAsync();

        var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/api/OAuthClients/{id}/generate-secret");
        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();
        var result = System.Text.Json.JsonSerializer.Deserialize<object>(content);

        _logger.LogInformation("Generated new secret for OAuth client {Id}", id);

        return result!;
    }

    public async Task<object> UpdateClientAsync(string id, UpdateClientDto dto)
    {
        var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
        var token = await GetAuthTokenAsync();

        var requestBody = new
        {
            dto.DisplayName,
            dto.RedirectUris,
            dto.PostLogoutRedirectUris,
            dto.Scopes,
            dto.GrantTypes,
            dto.ClientType,
            dto.ConsentType,
            dto.Status,
            dto.Description
        };

        var request = new HttpRequestMessage(HttpMethod.Put, $"{authServiceUrl}/api/OAuthClients/{id}")
        {
            Content = new StringContent(
                System.Text.Json.JsonSerializer.Serialize(requestBody),
                System.Text.Encoding.UTF8,
                "application/json")
        };
        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();

        _logger.LogInformation("Updated OAuth client {Id}", id);

        return new { message = "Client updated successfully" };
    }

    public async Task DeleteClientAsync(string id)
    {
        var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
        var token = await GetAuthTokenAsync();

        var request = new HttpRequestMessage(HttpMethod.Delete, $"{authServiceUrl}/api/OAuthClients/{id}");
        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();

        _logger.LogInformation("Deleted OAuth client {Id}", id);
    }

    public object GetClientOptions()
    {
        return new
        {
            clientTypes = new[]
            {
                new { value = "public", label = "Public (SPA, Mobile App)" },
                new { value = "confidential", label = "Confidential (Server-side)" }
            },
            consentTypes = new[]
            {
                new { value = "implicit", label = "Implicit" },
                new { value = "explicit", label = "Explicit" },
                new { value = "system", label = "System (Pre-authorized)" }
            },
            grantTypes = new[]
            {
                new { value = "authorization_code", label = "Authorization Code" },
                new { value = "client_credentials", label = "Client Credentials" },
                new { value = "refresh_token", label = "Refresh Token" },
                new { value = "password", label = "Resource Owner Password Credentials" }
            },
            scopes = new[]
            {
                new { value = "openid", label = "OpenID Connect" },
                new { value = "profile", label = "Profile" },
                new { value = "email", label = "Email" },
                new { value = "api", label = "API Access" },
                new { value = "offline_access", label = "Offline Access" }
            },
            statuses = new[]
            {
                new { value = "active", label = "Active" },
                new { value = "inactive", label = "Inactive" },
                new { value = "suspended", label = "Suspended" }
            }
        };
    }

    private async Task<string> GetAuthTokenAsync()
    {
        var authServiceUrl = _configuration["AuthService:Url"] ?? "http://localhost:5132";
        var clientId = _configuration["AuthService:ClientId"] ?? "console-client";
        var clientSecret = _configuration["AuthService:ClientSecret"] ?? "console-secret";

        var requestBody = new Dictionary<string, string>
        {
            { "grant_type", "client_credentials" },
            { "client_id", clientId },
            { "client_secret", clientSecret },
            { "scope", "api" }
        };

        var request = new HttpRequestMessage(HttpMethod.Post, $"{authServiceUrl}/connect/token")
        {
            Content = new FormUrlEncodedContent(requestBody)
        };

        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();
        var tokenResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(content);

        return tokenResponse!["access_token"].GetString() ?? string.Empty;
    }

    private static string GenerateSecureSecret(int length = 32)
    {
        var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        var bytes = RandomNumberGenerator.GetBytes(length);
        return new string(bytes.Select(b => chars[b % chars.Length]).ToArray());
    }
}
