using Books.Api.Banking;
using Books.Api.Commands.BankConnections;
using Books.Api.Domain.BankConnections;
using EventFlow;
using EventFlow.Aggregates.ExecutionResults;
using Microsoft.AspNetCore.Mvc;

namespace Books.Api.Controllers;

[ApiController]
[Route("api/banking")]
public class BankingController : ControllerBase
{
    private readonly ICommandBus _commandBus;
    private readonly IEnableBankingClient _bankingClient;
    private readonly ILogger<BankingController> _logger;
    private readonly IConfiguration _configuration;

    public BankingController(
        ICommandBus commandBus,
        IEnableBankingClient bankingClient,
        ILogger<BankingController> logger,
        IConfiguration configuration)
    {
        _commandBus = commandBus;
        _bankingClient = bankingClient;
        _logger = logger;
        _configuration = configuration;
    }

    /// <summary>
    /// OAuth callback from Enable Banking after user authorizes bank connection.
    /// </summary>
    [HttpGet("callback")]
    public async Task<IActionResult> Callback(
        [FromQuery] string? code,
        [FromQuery] string? state,
        [FromQuery] string? error,
        [FromQuery] string? error_description,
        CancellationToken ct)
    {
        var frontendBaseUrl = _configuration["Frontend:BaseUrl"] ?? "http://localhost:3000";
        var redirectUrl = $"{frontendBaseUrl}/indstillinger?tab=bankAccounts";

        // Handle error from bank
        if (!string.IsNullOrEmpty(error))
        {
            _logger.LogWarning("Bank authorization failed: {Error} - {Description}", error, error_description);
            return Redirect($"{redirectUrl}&error={Uri.EscapeDataString(error_description ?? error)}");
        }

        // Validate required parameters
        if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(state))
        {
            _logger.LogWarning("Missing code or state in callback");
            return Redirect($"{redirectUrl}&error=missing_parameters");
        }

        try
        {
            // TODO: Add proper CSRF/state validation. Currently the state parameter
            // is used as the connection ID, but it should also include a CSRF token
            // that is validated against the user session to prevent cross-site request
            // forgery attacks on the OAuth callback.
            // State contains the connection ID (set during StartBankConnection)
            var connectionId = state;

            // Get PSU headers from HttpContext (required by Enable Banking API)
            var psuIpAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
            var psuUserAgent = Request.Headers.UserAgent.ToString();

            // Exchange authorization code for session
            var session = await _bankingClient.CreateSessionAsync(code, psuIpAddress, psuUserAgent, ct);

            _logger.LogInformation(
                "Bank session created: {SessionId}, Bank: {Bank}, Accounts: {AccountCount}",
                session.SessionId,
                session.AspspName,
                session.Accounts.Count);

            // Complete the bank connection
            var command = new EstablishBankConnectionCommand(
                BankConnectionId.With(connectionId),
                session.SessionId,
                session.ValidUntil,
                session.Accounts.Select(a => new BankAccountInfo(
                    a.AccountId,
                    a.Iban,
                    a.Currency,
                    a.AccountName)).ToList());

            var result = await _commandBus.PublishAsync(command, ct);

            if (result is FailedExecutionResult failed)
            {
                _logger.LogError("Failed to complete bank connection: {Errors}", string.Join(", ", failed.Errors));
                return Redirect($"{redirectUrl}&error=completion_failed");
            }

            _logger.LogInformation("Bank connection {ConnectionId} completed successfully", connectionId);
            return Redirect($"{redirectUrl}&success=true");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error completing bank connection");
            return Redirect($"{redirectUrl}&error=internal_error");
        }
    }
}
