using System.Text.Json;
using Books.Api.Domain.BankConnections;
using Books.Api.EventFlow.ReadModels;
using Dapper;
using Npgsql;

namespace Books.Api.EventFlow.Repositories;

public class BankConnectionRepository(NpgsqlDataSource dataSource) : IBankConnectionRepository
{
    private const string SelectColumns = """
        aggregate_id AS Id,
        company_id AS CompanyId,
        aspsp_name AS AspspName,
        status AS Status,
        session_id AS SessionId,
        valid_until AS ValidUntil,
        accounts_json AS AccountsJson,
        failure_reason AS FailureReason,
        create_time AS CreatedAt,
        updated_time AS UpdatedAt,
        is_archived AS IsArchived
        """;

    public async Task<BankConnectionReadModelDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM bank_connection_read_models
            WHERE aggregate_id = @Id
            """;

        var raw = await connection.QuerySingleOrDefaultAsync<BankConnectionRawDto>(sql, new { Id = id });
        return raw != null ? MapToDto(raw) : null;
    }

    public async Task<IReadOnlyList<BankConnectionReadModelDto>> GetByCompanyIdAsync(string companyId, CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM bank_connection_read_models
            WHERE company_id = @CompanyId
              AND (is_archived = FALSE OR is_archived IS NULL)
            ORDER BY create_time DESC
            """;

        var result = await connection.QueryAsync<BankConnectionRawDto>(sql, new { CompanyId = companyId });
        return result.Select(MapToDto).ToList();
    }

    public async Task<IReadOnlyList<BankConnectionReadModelDto>> GetActiveByCompanyIdAsync(string companyId, CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM bank_connection_read_models
            WHERE company_id = @CompanyId
              AND status = 'established'
              AND valid_until > NOW()
              AND (is_archived = FALSE OR is_archived IS NULL)
            ORDER BY create_time DESC
            """;

        var result = await connection.QueryAsync<BankConnectionRawDto>(sql, new { CompanyId = companyId });
        return result.Select(MapToDto).ToList();
    }

    public async Task<IReadOnlyList<BankConnectionReadModelDto>> GetAllActiveAsync(CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM bank_connection_read_models
            WHERE status = 'established'
              AND valid_until > NOW()
              AND (is_archived = FALSE OR is_archived IS NULL)
            ORDER BY company_id, create_time DESC
            """;

        var result = await connection.QueryAsync<BankConnectionRawDto>(sql);
        return result.Select(MapToDto).ToList();
    }

    private static BankConnectionReadModelDto MapToDto(BankConnectionRawDto raw)
    {
        // Handle DateTime.MinValue as null (database might store 0001-01-01 instead of NULL)
        // Use DateTime.SpecifyKind to ensure UTC interpretation before creating DateTimeOffset
        DateTimeOffset? validUntil = raw.ValidUntil.HasValue && raw.ValidUntil.Value.Year > 1
            ? new DateTimeOffset(DateTime.SpecifyKind(raw.ValidUntil.Value, DateTimeKind.Utc))
            : null;

        return new BankConnectionReadModelDto
        {
            Id = raw.Id,
            CompanyId = raw.CompanyId,
            AspspName = raw.AspspName,
            Status = raw.Status,
            SessionId = raw.SessionId,
            ValidUntil = validUntil,
            Accounts = string.IsNullOrEmpty(raw.AccountsJson)
                ? null
                : JsonSerializer.Deserialize<List<BankAccountInfo>>(raw.AccountsJson),
            FailureReason = raw.FailureReason,
            CreatedAt = raw.CreatedAt,
            UpdatedAt = raw.UpdatedAt,
            IsArchived = raw.IsArchived
        };
    }

    private class BankConnectionRawDto
    {
        public string Id { get; set; } = string.Empty;
        public string CompanyId { get; set; } = string.Empty;
        public string AspspName { get; set; } = string.Empty;
        public string Status { get; set; } = string.Empty;
        public string? SessionId { get; set; }
        public DateTime? ValidUntil { get; set; }  // Use DateTime to avoid DateTimeOffset conversion issues
        public string? AccountsJson { get; set; }
        public string? FailureReason { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
        public bool IsArchived { get; set; }
    }
}
