using Books.Api.Domain.Accounts;
using Books.Api.Domain.Accounts.Events;
using Books.Api.EventFlow.Repositories;
using EventFlow.Aggregates;
using EventFlow.Subscribers;
using Microsoft.Extensions.Logging;
using Npgsql;

namespace Books.Api.Ledger;

/// <summary>
/// Async subscriber that syncs accounts from Books.Api to Ledger when created.
/// This ensures every account in the chart of accounts has a corresponding Ledger account
/// for posting transactions.
/// </summary>
public class LedgerAccountSyncSubscriber(
    NpgsqlDataSource dataSource,
    ICompanyRepository companyRepository,
    ILogger<LedgerAccountSyncSubscriber> logger)
    : ISubscribeAsynchronousTo<AccountAggregate, AccountId, AccountCreatedEvent>
{
    private const string InsertAccountSql = """
        INSERT INTO accounts (id, customer_id, account_type, currency, balance, status)
        VALUES ($1, $2, $3, $4, 0, 'active')
        ON CONFLICT (id) DO NOTHING
        """;

    public async Task HandleAsync(
        IDomainEvent<AccountAggregate, AccountId, AccountCreatedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        var accountIdString = domainEvent.AggregateIdentity.Value;

        // Extract UUID from AccountId format "account-{guid}"
        if (!TryParseAccountGuid(accountIdString, out var accountGuid))
        {
            logger.LogWarning(
                "Cannot sync account {AccountId} to Ledger - invalid ID format",
                accountIdString);
            return;
        }

        // Look up company to get currency
        var company = await companyRepository.GetByIdAsync(e.CompanyId, cancellationToken);
        if (company == null)
        {
            logger.LogWarning(
                "Cannot sync account {AccountId} to Ledger - company {CompanyId} not found",
                accountIdString, e.CompanyId);
            return;
        }

        // Map Books.Api AccountType to Ledger account_type
        var ledgerAccountType = MapAccountType(e.AccountType);

        // Parse company ID to GUID for customer_id
        if (!TryParseCompanyGuid(e.CompanyId, out var customerGuid))
        {
            logger.LogWarning(
                "Cannot sync account {AccountId} to Ledger - invalid company ID format: {CompanyId}",
                accountIdString, e.CompanyId);
            return;
        }

        try
        {
            await using var conn = await dataSource.OpenConnectionAsync(cancellationToken);
            await using var cmd = new NpgsqlCommand(InsertAccountSql, conn)
            {
                Parameters =
                {
                    new() { Value = accountGuid },
                    new() { Value = customerGuid },
                    new() { Value = ledgerAccountType },
                    new() { Value = company.Currency }
                }
            };

            var rowsAffected = await cmd.ExecuteNonQueryAsync(cancellationToken);

            if (rowsAffected > 0)
            {
                logger.LogInformation(
                    "Synced account {AccountNumber} ({AccountName}) to Ledger with ID {LedgerAccountId}",
                    e.AccountNumber, e.Name, accountGuid);
            }
            else
            {
                logger.LogDebug(
                    "Account {AccountNumber} already exists in Ledger (ID {LedgerAccountId})",
                    e.AccountNumber, accountGuid);
            }
        }
        catch (Exception ex)
        {
            logger.LogError(ex,
                "Failed to sync account {AccountId} ({AccountNumber}) to Ledger",
                accountIdString, e.AccountNumber);
            throw; // Re-throw to trigger retry via resilience strategy
        }
    }

    private static bool TryParseAccountGuid(string accountId, out Guid guid)
    {
        guid = Guid.Empty;

        // AccountId format: "account-{guid}"
        if (!accountId.StartsWith("account-", StringComparison.Ordinal))
            return false;

        var guidString = accountId["account-".Length..];
        return Guid.TryParse(guidString, out guid);
    }

    private static bool TryParseCompanyGuid(string companyId, out Guid guid)
    {
        guid = Guid.Empty;

        // CompanyId format: "company-{guid}"
        if (!companyId.StartsWith("company-", StringComparison.Ordinal))
            return false;

        var guidString = companyId["company-".Length..];
        return Guid.TryParse(guidString, out guid);
    }

    private static string MapAccountType(AccountType accountType) => accountType switch
    {
        AccountType.Asset => "asset",
        AccountType.Liability => "liability",
        AccountType.Equity => "equity",
        AccountType.Revenue => "revenue",
        AccountType.Expense => "expense",
        _ => "other"
    };
}
