using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
using Books.Api.Domain.BankConnections;
using Books.Api.Domain.BankConnections.Events;
using EventFlow.Aggregates;
using EventFlow.PostgreSql.ReadStores.Attributes;
using EventFlow.ReadStores;

namespace Books.Api.EventFlow.ReadModels;

[Table("bank_connection_read_models")]
public class BankConnectionReadModel : IReadModel,
    IAmReadModelFor<BankConnectionAggregate, BankConnectionId, BankConnectionInitiatedEvent>,
    IAmReadModelFor<BankConnectionAggregate, BankConnectionId, BankConnectionEstablishedEvent>,
    IAmReadModelFor<BankConnectionAggregate, BankConnectionId, BankConnectionFailedEvent>,
    IAmReadModelFor<BankConnectionAggregate, BankConnectionId, BankConnectionDisconnectedEvent>,
    IAmReadModelFor<BankConnectionAggregate, BankConnectionId, BankConnectionRefreshedEvent>,
    IAmReadModelFor<BankConnectionAggregate, BankConnectionId, BankAccountLinkedEvent>,
    IAmReadModelFor<BankConnectionAggregate, BankConnectionId, BankConnectionReInitiatedEvent>,
    IAmReadModelFor<BankConnectionAggregate, BankConnectionId, BankConnectionArchivedEvent>
{
    // EventFlow standard columns
    [PostgreSqlReadModelIdentityColumn]
    public string AggregateId { get; set; } = string.Empty;

    public DateTimeOffset CreateTime { get; set; }
    public DateTimeOffset UpdatedTime { get; set; }

    [PostgreSqlReadModelVersionColumn]
    public int LastAggregateSequenceNumber { get; set; }

    // Business columns
    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 DateTimeOffset? ValidUntil { get; set; }
    public string? AccountsJson { get; set; }
    public string? FailureReason { get; set; }
    public bool IsArchived { get; set; }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<BankConnectionAggregate, BankConnectionId, BankConnectionInitiatedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        AggregateId = domainEvent.AggregateIdentity.Value;
        CreateTime = domainEvent.Timestamp;
        UpdatedTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        CompanyId = e.CompanyId;
        AspspName = e.AspspName;
        Status = "initiated";
        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<BankConnectionAggregate, BankConnectionId, BankConnectionEstablishedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdatedTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        Status = "established";
        SessionId = e.SessionId;
        ValidUntil = e.ValidUntil;
        AccountsJson = JsonSerializer.Serialize(e.Accounts);
        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<BankConnectionAggregate, BankConnectionId, BankConnectionFailedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdatedTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        Status = "failed";
        FailureReason = e.Reason;
        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<BankConnectionAggregate, BankConnectionId, BankConnectionDisconnectedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdatedTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        Status = "disconnected";
        SessionId = null;
        FailureReason = e.Reason;
        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<BankConnectionAggregate, BankConnectionId, BankConnectionRefreshedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdatedTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        SessionId = e.NewSessionId;
        ValidUntil = e.ValidUntil;
        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<BankConnectionAggregate, BankConnectionId, BankAccountLinkedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdatedTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;

        // Update the linked account ID and import from date in the accounts JSON
        if (!string.IsNullOrEmpty(AccountsJson))
        {
            var accounts = JsonSerializer.Deserialize<List<BankAccountInfo>>(AccountsJson) ?? [];
            var accountIndex = accounts.FindIndex(a => a.AccountId == e.BankAccountId);
            if (accountIndex >= 0)
            {
                var account = accounts[accountIndex];
                accounts[accountIndex] = account with
                {
                    LinkedAccountId = e.LinkedAccountId,
                    ImportFromDate = e.ImportFromDate
                };
                AccountsJson = JsonSerializer.Serialize(accounts);
            }
        }

        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<BankConnectionAggregate, BankConnectionId, BankConnectionReInitiatedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        UpdatedTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        Status = "initiated"; // Reset to initiated, waiting for authorization
        SessionId = null;
        FailureReason = null;
        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<BankConnectionAggregate, BankConnectionId, BankConnectionArchivedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        UpdatedTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        IsArchived = true;
        return Task.CompletedTask;
    }
}
