using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using System.Security.Cryptography;

namespace Fengling.AuthService.Controllers;

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class OAuthClientsController : ControllerBase
{
    private readonly IOpenIddictApplicationManager _applicationManager;
    private readonly ILogger<OAuthClientsController> _logger;

    public OAuthClientsController(
        IOpenIddictApplicationManager applicationManager,
        ILogger<OAuthClientsController> logger)
    {
        _applicationManager = applicationManager;
        _logger = logger;
    }

    [HttpGet]
    public async Task<ActionResult<object>> GetClients(
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 10,
        [FromQuery] string? displayName = null,
        [FromQuery] string? clientId = null)
    {
        var applications = _applicationManager.ListAsync();
        var clientList = new List<object>();

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

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

            if (!string.IsNullOrEmpty(clientId) && !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 redirectUrisStrings = await _applicationManager.GetRedirectUrisAsync(application);
            var postLogoutRedirectUrisStrings = await _applicationManager.GetPostLogoutRedirectUrisAsync(application);
            var redirectUris = redirectUrisStrings;
            var postLogoutRedirectUris = postLogoutRedirectUrisStrings;

            clientList.Add(new
            {
                id = application,
                clientId = clientIdValue,
                displayName = displayNameValue,
                redirectUris = redirectUris.Select(u => u.ToString()).ToArray(),
                postLogoutRedirectUris = postLogoutRedirectUris.Select(u => u.ToString()).ToArray(),
                scopes = permissions
                    .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
                    .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length)),
                grantTypes = permissions
                    .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
                    .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)),
                clientType = clientType?.ToString(),
                consentType = consentType?.ToString(),
                status = "active",
                permissions
            });
        }

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

        return Ok(new
        {
            items = sortedClients,
            totalCount = clientList.Count,
            page,
            pageSize
        });
    }

    [HttpGet("options")]
    public ActionResult<object> GetClientOptions()
    {
        return Ok(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" }
            }
        });
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<object>> GetClient(string id)
    {
        var application = await _applicationManager.FindByIdAsync(id);
        if (application == null)
        {
            return NotFound();
        }

        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 Ok(new
        {
            id = id,
            clientId = clientIdValue,
            displayName = displayNameValue,
            redirectUris = redirectUris.Select(u => u.ToString()).ToArray(),
            postLogoutRedirectUris = postLogoutRedirectUris.Select(u => u.ToString()).ToArray(),
            scopes = permissions
                .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
                .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length)),
            grantTypes = permissions
                .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
                .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length)),
            clientType = clientType?.ToString(),
            consentType = consentType?.ToString(),
            status = "active",
            permissions
        });
    }

    [HttpPost]
    public async Task<ActionResult<object>> CreateClient([FromBody] CreateOAuthClientDto dto)
    {
        var existingClient = await _applicationManager.FindByClientIdAsync(dto.ClientId);
        if (existingClient != null)
        {
            return BadRequest(new { message = "Client ID 已存在", clientId = dto.ClientId });
        }

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

        var descriptor = new OpenIddictApplicationDescriptor
        {
            ClientId = dto.ClientId,
            ClientSecret = clientSecret,
            DisplayName = dto.DisplayName,
            ClientType = string.IsNullOrEmpty(dto.ClientType) ? "confidential" : dto.ClientType,
            ConsentType = string.IsNullOrEmpty(dto.ConsentType) ? "explicit" : dto.ConsentType,
        };

        descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization);
        descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.EndSession);
        descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token);
        descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection);

        foreach (var uri in (dto.RedirectUris ?? Array.Empty<string>()).Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
        {
            descriptor.RedirectUris.Add(new Uri(uri));
        }

        foreach (var uri in (dto.PostLogoutRedirectUris ?? Array.Empty<string>()).Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
        {
            descriptor.PostLogoutRedirectUris.Add(new Uri(uri));
        }

        foreach (var grantType in dto.GrantTypes ?? Array.Empty<string>())
        {
            descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.GrantType + grantType);
        }

        foreach (var scope in dto.Scopes ?? Array.Empty<string>())
        {
            descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope);
        }

        var application = await _applicationManager.CreateAsync(descriptor);
        var applicationId = await _applicationManager.GetIdAsync(application);

        return CreatedAtAction(nameof(GetClient), new { id = applicationId }, new
        {
            id = applicationId,
            clientId = dto.ClientId,
            clientSecret = clientSecret,
            displayName = dto.DisplayName,
            status = "active"
        });
    }

    [HttpPost("{id}/generate-secret")]
    public async Task<ActionResult<object>> GenerateSecret(string id)
    {
        var application = await _applicationManager.FindByIdAsync(id);
        if (application == null)
        {
            return NotFound();
        }

        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);

        var newSecret = GenerateSecureSecret();

        var descriptor = new OpenIddictApplicationDescriptor
        {
            ClientId = clientIdValue,
            ClientSecret = newSecret,
            DisplayName = displayNameValue,
            ClientType = clientType,
            ConsentType = consentType,
        };

        foreach (var permission in permissions)
        {
            descriptor.Permissions.Add(permission);
        }

        foreach (var uriString in redirectUris)
        {
            if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
            {
                descriptor.RedirectUris.Add(uri);
            }
        }

        foreach (var uriString in postLogoutRedirectUris)
        {
            if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
            {
                descriptor.PostLogoutRedirectUris.Add(uri);
            }
        }

        await _applicationManager.UpdateAsync(application, descriptor);

        return Ok(new
        {
            clientId = clientIdValue,
            clientSecret = newSecret,
            message = "新密钥已生成，请妥善保管，刷新后将无法再次查看"
        });
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteClient(string id)
    {
        var application = await _applicationManager.FindByIdAsync(id);
        if (application == null)
        {
            return NotFound();
        }

        var clientIdValue = await _applicationManager.GetClientIdAsync(application);

        try
        {
            await _applicationManager.DeleteAsync(application);
            _logger.LogInformation("Deleted OpenIddict application {ClientId}", clientIdValue);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to delete OpenIddict application for client {ClientId}", clientIdValue);
            return BadRequest(new { message = ex.Message });
        }

        return NoContent();
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateClient(string id, [FromBody] UpdateOAuthClientDto dto)
    {
        var application = await _applicationManager.FindByIdAsync(id);
        if (application == null)
        {
            return NotFound();
        }

        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 redirectUris = await _applicationManager.GetRedirectUrisAsync(application);
        var postLogoutRedirectUris = await _applicationManager.GetPostLogoutRedirectUrisAsync(application);
        var existingPermissions = await _applicationManager.GetPermissionsAsync(application);

        var descriptor = new OpenIddictApplicationDescriptor
        {
            ClientId = clientIdValue,
            DisplayName = dto.DisplayName ?? displayNameValue,
            ClientType = string.IsNullOrEmpty(dto.ClientType) ? clientType?.ToString() : dto.ClientType,
            ConsentType = string.IsNullOrEmpty(dto.ConsentType) ? consentType?.ToString() : dto.ConsentType,
        };

        descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization);
        descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.EndSession);
        descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token);
        descriptor.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection);

        if (dto.RedirectUris != null)
        {
            foreach (var uri in dto.RedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
            {
                descriptor.RedirectUris.Add(new Uri(uri));
            }
        }
        else
        {
            foreach (var uriString in redirectUris)
            {
                if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
                {
                    descriptor.RedirectUris.Add(uri);
                }
            }
        }

        if (dto.PostLogoutRedirectUris != null)
        {
            foreach (var uri in dto.PostLogoutRedirectUris.Where(u => Uri.TryCreate(u, UriKind.Absolute, out _)))
            {
                descriptor.PostLogoutRedirectUris.Add(new Uri(uri));
            }
        }
        else
        {
            foreach (var uriString in postLogoutRedirectUris)
            {
                if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
                {
                    descriptor.PostLogoutRedirectUris.Add(uri);
                }
            }
        }

        var grantTypes = dto.GrantTypes ?? existingPermissions
            .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType))
            .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.GrantType.Length));

        var scopes = dto.Scopes ?? existingPermissions
            .Where(p => p.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope))
            .Select(p => p.Substring(OpenIddictConstants.Permissions.Prefixes.Scope.Length));

        foreach (var grantType in grantTypes)
        {
            descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.GrantType + grantType);
        }

        foreach (var scope in scopes)
        {
            descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope);
        }

        await _applicationManager.UpdateAsync(application, descriptor);

        return NoContent();
    }

    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());
    }
}

public class CreateOAuthClientDto
{
    public string ClientId { get; set; } = string.Empty;
    public string? ClientSecret { get; set; }
    public string DisplayName { get; set; } = string.Empty;
    public string[]? RedirectUris { get; set; }
    public string[]? PostLogoutRedirectUris { get; set; }
    public string[]? Scopes { get; set; }
    public string[]? GrantTypes { get; set; }
    public string? ClientType { get; set; }
    public string? ConsentType { get; set; }
    public string? Status { get; set; }
    public string? Description { get; set; }
}

public class UpdateOAuthClientDto
{
    public string? DisplayName { get; set; }
    public string[]? RedirectUris { get; set; }
    public string[]? PostLogoutRedirectUris { get; set; }
    public string[]? Scopes { get; set; }
    public string[]? GrantTypes { get; set; }
    public string? ClientType { get; set; }
    public string? ConsentType { get; set; }
    public string? Status { get; set; }
    public string? Description { get; set; }
}
