using System.Text.Json;
using Dapper;
using KArtSell.Modules.ModelOperations.TradeExecution;
using Microsoft.Extensions.Logging;
using Npgsql;
using Xunit;

namespace KArtSell.Integration.Tests.TradeExecution;

[Collection("Database")]
public class TradeExecutionTests : IAsyncLifetime
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly ILogger<TradeSql> _logger;

    public TradeExecutionTests()
    {
        var connectionString = Environment.GetEnvironmentVariable("KARTSELL_POSTGRES")
            ?? "Host=localhost;Port=5432;Database=kartselldb;Username=kartsell;Password=kartsell4321@!";
        var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
        _dataSource = dataSourceBuilder.Build();
        _logger = new LoggerFactory().CreateLogger<TradeSql>();
    }

    public async Task InitializeAsync()
    {
        await using var connection = await _dataSource.OpenConnectionAsync();
    }

    public async Task DisposeAsync()
    {
        await _dataSource.DisposeAsync();
    }

    private async Task<Guid> SeedSellDecisionAsync()
    {
        await using var connection = await _dataSource.OpenConnectionAsync();
        var modelId = Guid.NewGuid();
        await connection.ExecuteAsync(
            "INSERT INTO model_operations.models (id, ticker, correlation_id) VALUES (@Id, @Ticker, @CorrelationId)",
            new { Id = modelId, Ticker = "TEST", CorrelationId = Guid.NewGuid() });

        var sellDecisionId = Guid.NewGuid();
        await connection.ExecuteAsync(
            """
            INSERT INTO model_operations.sell_decisions
            (id, model_id, status, created_by, published_at, correlation_id)
            VALUES (@Id, @ModelId, 'PENDING', 'test@company.com', NOW(), @CorrelationId)
            """,
            new { Id = sellDecisionId, ModelId = modelId, CorrelationId = Guid.NewGuid() });

        return sellDecisionId;
    }

    [Fact]
    public async Task CreateTrade_WithValidData_ShouldInsertSuccessfully()
    {
        var sql = new TradeSql(_dataSource, _logger);
        var sellDecisionId = await SeedSellDecisionAsync();
        var correlationId = Guid.NewGuid();

        var trade = Trade.Create(sellDecisionId, 1000, correlationId, DateTime.UtcNow);

        await sql.InsertTradeAsync(trade);

        var retrieved = await sql.GetTradeByIdAsync(trade.Id, correlationId);

        Assert.NotNull(retrieved);
        Assert.Equal(trade.Id, retrieved.Id);
        Assert.Equal(TradeStatus.Pending, retrieved.Status);
        Assert.Equal(1000, retrieved.Quantity);
    }

    [Fact]
    public async Task MarkSubmitted_UpdatesTradeStatusCorrectly()
    {
        var sql = new TradeSql(_dataSource, _logger);
        var correlationId = Guid.NewGuid();
        var trade = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow);

        await sql.InsertTradeAsync(trade);

        var response = JsonDocument.Parse("{}").RootElement;
        trade.MarkSubmitted("KIS-ORDER-123", response);

        await sql.UpdateTradeStatusAsync(trade, response, null);

        var retrieved = await sql.GetTradeByIdAsync(trade.Id, correlationId);

        Assert.NotNull(retrieved);
        Assert.Equal(TradeStatus.Submitted, retrieved.Status);
        Assert.Equal("KIS-ORDER-123", retrieved.KisOrderId);
    }

    [Fact]
    public async Task MarkFilled_CalculatesCorrectTotals()
    {
        var sql = new TradeSql(_dataSource, _logger);
        var correlationId = Guid.NewGuid();
        var trade = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow);

        await sql.InsertTradeAsync(trade);

        var response = JsonDocument.Parse("{}").RootElement;
        trade.MarkFilled(1000, 49.95m, response, DateTime.UtcNow);

        await sql.UpdateTradeStatusAsync(trade, response, null);

        var retrieved = await sql.GetTradeByIdAsync(trade.Id, correlationId);

        Assert.NotNull(retrieved);
        Assert.Equal(TradeStatus.FullyFilled, retrieved.Status);
        Assert.Equal(1000, retrieved.ExecutedQuantity);
        Assert.Equal(49.95m, retrieved.UnitPrice);
        Assert.Equal(49950m, retrieved.TotalAmount);
    }

    [Fact]
    public async Task GetTradesByStatus_ReturnsCorrectTrades()
    {
        var sql = new TradeSql(_dataSource, _logger);
        var correlationId = Guid.NewGuid();

        var trade1 = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow);
        var trade2 = Trade.Create(await SeedSellDecisionAsync(), 2000, correlationId, DateTime.UtcNow);

        await sql.InsertTradeAsync(trade1);
        await sql.InsertTradeAsync(trade2);

        var trades = await sql.GetTradesByStatusAsync(TradeStatus.Pending, correlationId);

        Assert.NotEmpty(trades);
        Assert.Contains(trades, t => t.Id == trade1.Id);
        Assert.Contains(trades, t => t.Id == trade2.Id);
    }

    [Fact]
    public async Task MarkConfirmed_SetsSettlementTimestamp()
    {
        var sql = new TradeSql(_dataSource, _logger);
        var correlationId = Guid.NewGuid();
        var trade = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow);

        await sql.InsertTradeAsync(trade);

        trade.TotalAmount = 49950m;
        trade.MarkConfirmed(DateTime.UtcNow, 50m);

        await sql.UpdateTradeStatusAsync(trade, null, null);

        var retrieved = await sql.GetTradeByIdAsync(trade.Id, correlationId);

        Assert.NotNull(retrieved);
        Assert.Equal(TradeStatus.Confirmed, retrieved.Status);
        Assert.Equal(50m, retrieved.Commission);
        Assert.Equal(49900m, retrieved.NetProceeds);
    }

    [Fact]
    public async Task TradeStatusHistory_TracksAllTransitions()
    {
        var sql = new TradeSql(_dataSource, _logger);
        var correlationId = Guid.NewGuid();
        var trade = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow);

        await sql.InsertTradeAsync(trade);

        var response = JsonDocument.Parse("{}").RootElement;
        trade.MarkSubmitted("KIS-1", response);
        await sql.UpdateTradeStatusAsync(trade, response, null);

        trade.MarkAccepted(response);
        await sql.UpdateTradeStatusAsync(trade, response, null);

        var retrieved = await sql.GetTradeByIdAsync(trade.Id, correlationId);

        Assert.NotNull(retrieved);
        Assert.Equal(TradeStatus.Accepted, retrieved.Status);
    }

    [Fact]
    public async Task CountTradesByStatus_ReturnsAccurateCount()
    {
        var sql = new TradeSql(_dataSource, _logger);
        var correlationId = Guid.NewGuid();

        var trade1 = Trade.Create(await SeedSellDecisionAsync(), 1000, correlationId, DateTime.UtcNow);
        var trade2 = Trade.Create(await SeedSellDecisionAsync(), 2000, correlationId, DateTime.UtcNow);

        await sql.InsertTradeAsync(trade1);
        await sql.InsertTradeAsync(trade2);

        var count = await sql.CountTradesByStatusAsync(TradeStatus.Pending);

        Assert.True(count >= 2);
    }

    [Fact]
    public void ErrorClassification_TransientErrors_Identified()
    {
        var ex = new KisTradeExecutionException(
            "Timeout",
            ErrorClassification.Transient
        );

        Assert.Equal(ErrorClassification.Transient, ex.Classification);
    }

    [Fact]
    public void ErrorClassification_PermanentErrors_Identified()
    {
        var ex = new KisTradeExecutionException(
            "Invalid order",
            ErrorClassification.Permanent
        );

        Assert.Equal(ErrorClassification.Permanent, ex.Classification);
    }

    [Fact]
    public void ErrorClassification_LiquidityErrors_Identified()
    {
        var ex = new KisTradeExecutionException(
            "Insufficient liquidity",
            ErrorClassification.Liquidity
        );

        Assert.Equal(ErrorClassification.Liquidity, ex.Classification);
    }

    [Fact]
    public void Trade_StateTransitions_ValidSequence()
    {
        var trade = Trade.Create(Guid.NewGuid(), 1000, Guid.NewGuid(), DateTime.UtcNow);

        Assert.Equal(TradeStatus.Pending, trade.Status);

        var response = JsonDocument.Parse("{}").RootElement;
        trade.MarkSubmitted("KIS-123", response);
        Assert.Equal(TradeStatus.Submitted, trade.Status);

        trade.MarkAccepted(response);
        Assert.Equal(TradeStatus.Accepted, trade.Status);

        trade.MarkFilled(1000, 49.95m, response, DateTime.UtcNow);
        Assert.Equal(TradeStatus.FullyFilled, trade.Status);

        trade.MarkConfirmed(DateTime.UtcNow, 50m);
        Assert.Equal(TradeStatus.Confirmed, trade.Status);

        trade.MarkReconciled();
        Assert.Equal(TradeStatus.Reconciled, trade.Status);
    }

    [Fact]
    public void Trade_PartialFill_StatusCorrect()
    {
        var trade = Trade.Create(Guid.NewGuid(), 1000, Guid.NewGuid(), DateTime.UtcNow);
        var response = JsonDocument.Parse("{}").RootElement;

        trade.MarkFilled(500, 49.95m, response, DateTime.UtcNow);

        Assert.Equal(TradeStatus.PartiallyFilled, trade.Status);
        Assert.Equal(500, trade.ExecutedQuantity);
    }

    [Fact]
    public void Trade_RevisionIncrementsOnStateChange()
    {
        var trade = Trade.Create(Guid.NewGuid(), 1000, Guid.NewGuid(), DateTime.UtcNow);
        var initialRevision = trade.Revision;

        var response = JsonDocument.Parse("{}").RootElement;
        trade.MarkSubmitted("KIS-123", response);

        Assert.Equal(initialRevision + 1, trade.Revision);
    }
}
