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

namespace Books.Api.EventFlow.ReadModels;

[Table("order_read_models")]
public class OrderReadModel : IReadModel,
    IAmReadModelFor<OrderAggregate, OrderId, OrderCreatedEvent>,
    IAmReadModelFor<OrderAggregate, OrderId, OrderLineAddedEvent>,
    IAmReadModelFor<OrderAggregate, OrderId, OrderLineUpdatedEvent>,
    IAmReadModelFor<OrderAggregate, OrderId, OrderLineRemovedEvent>,
    IAmReadModelFor<OrderAggregate, OrderId, OrderConfirmedEvent>,
    IAmReadModelFor<OrderAggregate, OrderId, OrderLinesInvoicedEvent>,
    IAmReadModelFor<OrderAggregate, OrderId, OrderCancelledEvent>
{
    // EventFlow standard columns
    [PostgreSqlReadModelIdentityColumn]
    public string AggregateId { get; set; } = string.Empty;

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

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

    // Company and fiscal year
    public string CompanyId { get; set; } = string.Empty;
    public string FiscalYearId { get; set; } = string.Empty;

    // Customer reference
    public string CustomerId { get; set; } = string.Empty;
    public string CustomerName { get; set; } = string.Empty;
    public string CustomerNumber { get; set; } = string.Empty;

    // Order identification
    public string OrderNumber { get; set; } = string.Empty;
    public DateTime? OrderDate { get; set; }
    public DateTime? ExpectedDeliveryDate { get; set; }

    // Status
    public string Status { get; set; } = "draft";

    // Amounts (set when confirmed)
    public decimal AmountExVat { get; set; }
    public decimal AmountVat { get; set; }
    public decimal AmountTotal { get; set; }

    // Currency
    public string Currency { get; set; } = "DKK";

    // Lines (stored as JSON string)
    public string Lines { get; set; } = "[]";

    // Invoice reference (when converted) - stores last invoice created
    public string? InvoiceId { get; set; }
    public string? InvoiceNumber { get; set; }

    // Notes and reference
    public string? Notes { get; set; }
    public string? Reference { get; set; }

    // Status timestamps
    public DateTimeOffset? ConfirmedAt { get; set; }
    public string? ConfirmedBy { get; set; }
    public DateTimeOffset? InvoicedAt { get; set; }
    public string? InvoicedBy { get; set; }
    public DateTimeOffset? CancelledAt { get; set; }
    public string? CancelledBy { get; set; }
    public string? CancelledReason { get; set; }

    // Audit
    public string CreatedBy { get; set; } = string.Empty;
    public string? UpdatedBy { get; set; }

    // Internal list for line tracking
    [NotMapped]
    private List<OrderLineDto>? _linesList;

    private List<OrderLineDto> GetLinesList()
    {
        if (_linesList == null)
        {
            _linesList = string.IsNullOrEmpty(Lines)
                ? []
                : JsonSerializer.Deserialize<List<OrderLineDto>>(Lines) ?? [];
        }
        return _linesList;
    }

    /// <summary>
    /// Returns the order lines for external access.
    /// </summary>
    public IReadOnlyList<OrderLineDto> GetLines() => GetLinesList();

    private void SaveLines()
    {
        Lines = JsonSerializer.Serialize(_linesList ?? []);
        RecalculateAmounts();
    }

    private void RecalculateAmounts()
    {
        var lines = GetLinesList();
        AmountExVat = lines.Sum(l => l.AmountExVat);
        AmountVat = lines.Sum(l => l.AmountVat);
        AmountTotal = lines.Sum(l => l.AmountTotal);
    }

    #region Apply Methods

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<OrderAggregate, OrderId, OrderCreatedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        AggregateId = domainEvent.AggregateIdentity.Value;
        CreateTime = domainEvent.Timestamp;
        UpdateTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        CompanyId = e.CompanyId;
        FiscalYearId = e.FiscalYearId;
        CustomerId = e.CustomerId;
        CustomerName = e.CustomerName;
        CustomerNumber = e.CustomerNumber;
        OrderNumber = e.OrderNumber;
        OrderDate = e.OrderDate.ToDateTime(TimeOnly.MinValue);
        ExpectedDeliveryDate = e.ExpectedDeliveryDate?.ToDateTime(TimeOnly.MinValue);
        Currency = e.Currency;
        Notes = e.Notes;
        Reference = e.Reference;
        CreatedBy = e.CreatedBy;
        Status = "draft";

        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<OrderAggregate, OrderId, OrderLineAddedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdateTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;

        var lines = GetLinesList();
        lines.Add(new OrderLineDto
        {
            LineNumber = e.LineNumber,
            ProductId = e.ProductId,
            Description = e.Description,
            Quantity = e.Quantity,
            Unit = e.Unit,
            UnitPrice = e.UnitPrice,
            DiscountPercent = e.DiscountPercent,
            VatCode = e.VatCode,
            AccountId = e.AccountId,
            IsInvoiced = false
        });
        SaveLines();

        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<OrderAggregate, OrderId, OrderLineUpdatedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdateTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;

        var lines = GetLinesList();
        var existingIndex = lines.FindIndex(l => l.LineNumber == e.LineNumber);
        if (existingIndex >= 0)
        {
            var existing = lines[existingIndex];
            lines[existingIndex] = new OrderLineDto
            {
                LineNumber = e.LineNumber,
                ProductId = e.ProductId,
                Description = e.Description,
                Quantity = e.Quantity,
                Unit = e.Unit,
                UnitPrice = e.UnitPrice,
                DiscountPercent = e.DiscountPercent,
                VatCode = e.VatCode,
                AccountId = e.AccountId,
                IsInvoiced = existing.IsInvoiced,
                InvoiceId = existing.InvoiceId,
                InvoicedAt = existing.InvoicedAt
            };
        }
        SaveLines();

        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<OrderAggregate, OrderId, OrderLineRemovedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdateTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;

        var lines = GetLinesList();
        lines.RemoveAll(l => l.LineNumber == e.LineNumber);
        SaveLines();

        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<OrderAggregate, OrderId, OrderConfirmedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdateTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        Status = "confirmed";
        AmountExVat = e.AmountExVat;
        AmountVat = e.AmountVat;
        AmountTotal = e.AmountTotal;
        ConfirmedAt = e.ConfirmedAt;
        ConfirmedBy = e.ConfirmedBy;

        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<OrderAggregate, OrderId, OrderLinesInvoicedEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdateTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        Status = e.NewStatus.ToStringValue();

        // Update invoice reference (to the latest invoice)
        InvoiceId = e.InvoiceId;
        InvoiceNumber = e.InvoiceNumber;
        InvoicedAt = e.InvoicedAt;
        InvoicedBy = e.InvoicedBy;

        // Mark the specific lines as invoiced
        var lines = GetLinesList();
        foreach (var lineNumber in e.LineNumbers)
        {
            var lineIndex = lines.FindIndex(l => l.LineNumber == lineNumber);
            if (lineIndex >= 0)
            {
                var line = lines[lineIndex];
                lines[lineIndex] = line with
                {
                    IsInvoiced = true,
                    InvoiceId = e.InvoiceId,
                    InvoicedAt = e.InvoicedAt
                };
            }
        }
        SaveLines();

        return Task.CompletedTask;
    }

    public Task ApplyAsync(
        IReadModelContext context,
        IDomainEvent<OrderAggregate, OrderId, OrderCancelledEvent> domainEvent,
        CancellationToken cancellationToken)
    {
        var e = domainEvent.AggregateEvent;
        UpdateTime = domainEvent.Timestamp;
        LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
        Status = "cancelled";
        CancelledAt = e.CancelledAt;
        CancelledBy = e.CancelledBy;
        CancelledReason = e.Reason;

        return Task.CompletedTask;
    }

    #endregion
}
