using Predictalytics.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace Predictalytics.Infrastructure.Data;

public class AppDbContext : DbContext
{
    public DbSet<Trader> Traders => Set<Trader>();
    public DbSet<Trade> Trades => Set<Trade>();
    public DbSet<Event> Events => Set<Event>();
    public DbSet<Market> Markets => Set<Market>();
    public DbSet<MarketOutcome> MarketOutcomes => Set<MarketOutcome>();
    public DbSet<TraderScore> TraderScores => Set<TraderScore>();
    public DbSet<WatchlistEntry> WatchlistEntries => Set<WatchlistEntry>();
    public DbSet<Alert> Alerts => Set<Alert>();
    public DbSet<PlatformConfig> PlatformConfigs => Set<PlatformConfig>();
    public DbSet<TraderAnalytics> TraderAnalytics => Set<TraderAnalytics>();
    public DbSet<MarketAnalytics> MarketAnalytics => Set<MarketAnalytics>();
    public DbSet<TraderPosition> TraderPositions => Set<TraderPosition>();
    public DbSet<MarketOutcomePriceSnapshot> MarketOutcomePriceSnapshots => Set<MarketOutcomePriceSnapshot>();
    public DbSet<TraderDailySnapshot> TraderDailySnapshots => Set<TraderDailySnapshot>();
    public DbSet<TraderCategoryPerformance> TraderCategoryPerformances => Set<TraderCategoryPerformance>();
    public DbSet<TradeContext> TradeContexts => Set<TradeContext>();
    public DbSet<BackgroundJob> BackgroundJobs => Set<BackgroundJob>();
    public DbSet<TraderTrait> TraderTraits => Set<TraderTrait>();
    public DbSet<TraderWindowMetrics> TraderWindowMetrics => Set<TraderWindowMetrics>();

    private readonly bool _isReadOnly;

    public AppDbContext(DbContextOptions<AppDbContext> options, Microsoft.Extensions.Configuration.IConfiguration? configuration = null) 
        : base(options) 
    {
        _isReadOnly = configuration?.GetValue<bool>("ApiSettings:ReadOnlyDatabase", false) ?? false;
    }

    public override int SaveChanges()
    {
        if (_isReadOnly)
        {
            throw new System.InvalidOperationException("Database is configured as Read-Only.");
        }
        return base.SaveChanges();
    }

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        if (_isReadOnly)
        {
            throw new System.InvalidOperationException("Database is configured as Read-Only.");
        }
        return base.SaveChanges(acceptAllChangesOnSuccess);
    }

    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        if (_isReadOnly)
        {
            throw new System.InvalidOperationException("Database is configured as Read-Only.");
        }
        return base.SaveChangesAsync(cancellationToken);
    }

    public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
    {
        if (_isReadOnly)
        {
            throw new System.InvalidOperationException("Database is configured as Read-Only.");
        }
        return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    }

    protected override void OnModelCreating(ModelBuilder mb)
    {
        // Trader
        mb.Entity<Trader>(e =>
        {
            e.HasKey(t => t.Id);
            e.HasIndex(t => new { t.Platform, t.PlatformUserId }).IsUnique();
            e.Property(t => t.PlatformUserId).HasMaxLength(128);
            e.Property(t => t.DisplayName).HasMaxLength(256);
            e.Property(t => t.TotalPnl).HasPrecision(18, 4);
            e.Property(t => t.WinRate).HasPrecision(8, 4);
            e.HasOne(t => t.CurrentScore).WithOne(s => s.Trader)
                .HasForeignKey<TraderScore>(s => s.TraderId).OnDelete(DeleteBehavior.Cascade);
        });

        // TraderTrait
        mb.Entity<TraderTrait>(e =>
        {
            e.HasKey(t => t.Id);
            e.HasIndex(t => new { t.TraderId, t.Trait }).IsUnique();
            e.Property(t => t.Value).HasPrecision(18, 4);
            e.HasOne(t => t.Trader).WithMany(tr => tr.Traits)
                .HasForeignKey(t => t.TraderId).OnDelete(DeleteBehavior.Cascade);
        });

        // TraderWindowMetrics
        mb.Entity<TraderWindowMetrics>(e =>
        {
            e.HasKey(t => t.Id);
            e.HasIndex(t => new { t.TraderId, t.WindowStart, t.WindowEnd }).IsUnique();
            e.Property(t => t.WinRate).HasPrecision(8, 4);
            e.Property(t => t.AvgReturnPct).HasPrecision(18, 4);
            e.Property(t => t.MedianWinReturnPct).HasPrecision(18, 4);
            e.Property(t => t.MedianLossReturnPct).HasPrecision(18, 4);
            e.Property(t => t.ProfitFactor).HasPrecision(18, 4);
            e.HasOne(t => t.Trader).WithMany()
                .HasForeignKey(t => t.TraderId).OnDelete(DeleteBehavior.Cascade);
        });

        // Trade
        mb.Entity<Trade>(e =>
        {
            e.HasKey(t => t.Id);
            e.HasIndex(t => new { t.Platform, t.PlatformTradeId }).IsUnique();
            e.HasIndex(t => t.TraderId);
            e.HasIndex(t => t.ExecutedAt);
            e.HasIndex(t => t.AssetId);
            e.HasIndex(t => t.DbMarketId);
            // PlatformTradeId: legacy data up to 256 chars; new trades use shorter format
            e.Property(t => t.PlatformTradeId).HasMaxLength(256);
            // MarketId: Polymarket ConditionId is always 66 hex chars
            e.Property(t => t.MarketId).HasMaxLength(66);
            // AssetId: clobTokenId; Polymarket uses decimal strings up to 78 chars
            e.Property(t => t.AssetId).HasMaxLength(80);
            // Outcome: labels can be long (e.g. anime titles or sports match descriptions)
            e.Property(t => t.Outcome).HasMaxLength(128);
            // Price: 0.00–1.00 on prediction markets, 6 decimals sufficient
            e.Property(t => t.Price).HasPrecision(18, 6);
            // Size: number of shares, needs more integer digits
            e.Property(t => t.Size).HasPrecision(14, 6);
            e.Property(t => t.Amount).HasPrecision(18, 4);
            // TransactionHash: 0x + 64 hex = 66 chars
            e.Property(t => t.TransactionHash).HasMaxLength(66);
            e.HasOne(t => t.Trader).WithMany(tr => tr.Trades).HasForeignKey(t => t.TraderId);
            e.HasOne(t => t.MarketOutcome).WithMany().HasForeignKey(t => t.MarketOutcomeId)
                .OnDelete(DeleteBehavior.SetNull);
            e.HasOne(t => t.DbMarket).WithMany().HasForeignKey(t => t.DbMarketId)
                .OnDelete(DeleteBehavior.SetNull);
        });

        // Event
        mb.Entity<Event>(e =>
        {
            e.HasKey(ev => ev.Id);
            e.HasIndex(ev => new { ev.Platform, ev.PlatformEventId }).IsUnique();
            e.Property(ev => ev.Slug).HasMaxLength(512);
            e.Property(ev => ev.Title).HasMaxLength(1024);
            e.Property(ev => ev.Description).HasMaxLength(4096);
            e.Property(ev => ev.ImageUrl).HasMaxLength(1024);
            e.Property(ev => ev.Tags).HasMaxLength(1024);
            e.HasMany(ev => ev.Markets).WithOne(m => m.Event).HasForeignKey(m => m.EventId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Market
        mb.Entity<Market>(e =>
        {
            e.HasKey(m => m.Id);
            e.HasIndex(m => new { m.Platform, m.PlatformMarketId }).IsUnique();
            e.Property(m => m.ConditionId).HasMaxLength(256);
            e.Property(m => m.QuestionId).HasMaxLength(256);
            e.Property(m => m.MarketSlug).HasMaxLength(512);
            e.Property(m => m.Question).HasMaxLength(1024);
            e.Property(m => m.Description).HasMaxLength(4096);
            e.Property(m => m.ImageUrl).HasMaxLength(1024);
            e.Property(m => m.Category).HasConversion<string>().HasMaxLength(64);
            e.Property(m => m.Subcategory).HasMaxLength(128);
            e.Property(m => m.Volume).HasPrecision(18, 4);
            e.Property(m => m.Volume24h).HasPrecision(18, 4);
            e.Property(m => m.Liquidity).HasPrecision(18, 4);
            e.HasMany(m => m.Outcomes).WithOne(o => o.Market).HasForeignKey(o => o.MarketId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // MarketOutcome
        mb.Entity<MarketOutcome>(e =>
        {
            e.HasKey(o => o.Id);
            e.HasIndex(o => o.TokenId);
            e.HasIndex(o => new { o.MarketId, o.OutcomeIndex }).IsUnique();
            e.Property(o => o.Label).HasMaxLength(256);
            e.Property(o => o.TokenId).HasMaxLength(256);
            e.Property(o => o.CurrentPrice).HasPrecision(18, 8);
        });

        // TraderScore
        mb.Entity<TraderScore>(e =>
        {
            e.HasKey(s => s.Id);
            e.HasIndex(s => s.TraderId).IsUnique();
            e.Property(s => s.ActivityScore).HasPrecision(5, 2);
            e.Property(s => s.QualityScore).HasPrecision(5, 2);
            e.Property(s => s.CombinedScore).HasPrecision(5, 2);
            e.Property(s => s.VolumeScore).HasPrecision(5, 2);
            e.Property(s => s.TimingScore).HasPrecision(5, 2);
            e.HasOne(s => s.Trader).WithOne(t => t.CurrentScore).HasForeignKey<TraderScore>(s => s.TraderId).OnDelete(DeleteBehavior.Cascade);
        });

        // WatchlistEntry
        mb.Entity<WatchlistEntry>(e =>
        {
            e.HasKey(w => w.Id);
            e.HasIndex(w => w.TraderId).IsUnique();
            e.Property(w => w.Label).HasMaxLength(256);
            e.HasOne(w => w.Trader).WithMany(t => t.WatchlistEntries).HasForeignKey(w => w.TraderId);
        });

        // Alert
        mb.Entity<Alert>(e =>
        {
            e.HasKey(a => a.Id);
            e.HasIndex(a => a.CreatedAt);
            e.Property(a => a.Title).HasMaxLength(512);
            e.Property(a => a.Message).HasMaxLength(4096);
            e.HasOne(a => a.Trader).WithMany().HasForeignKey(a => a.TraderId).OnDelete(DeleteBehavior.SetNull);
        });

        // TraderCategoryPerformance
        mb.Entity<TraderCategoryPerformance>(e =>
        {
            e.HasKey(tcp => tcp.Id);
            e.HasOne(tcp => tcp.Trader).WithMany(t => t.CategoryPerformances).HasForeignKey(tcp => tcp.TraderId).OnDelete(DeleteBehavior.Cascade);
            e.Property(tcp => tcp.Category).HasConversion<string>().HasMaxLength(64);
            e.Property(tcp => tcp.Subcategory).HasMaxLength(128);
            e.Property(tcp => tcp.TotalVolume).HasPrecision(18, 4);
            e.Property(tcp => tcp.TotalPnL).HasPrecision(18, 4);
            e.HasIndex(tcp => new { tcp.TraderId, tcp.Category, tcp.Subcategory }).IsUnique();
        });

        // TradeContext
        mb.Entity<TradeContext>(e =>
        {
            e.HasKey(tc => tc.Id);
            e.HasOne(tc => tc.Trade).WithOne(t => t.Context).HasForeignKey<TradeContext>(tc => tc.TradeId).OnDelete(DeleteBehavior.Cascade);
            e.Property(tc => tc.PriceBefore1m).HasPrecision(18, 4);
            e.Property(tc => tc.PriceAfter1m).HasPrecision(18, 4);
            e.Property(tc => tc.EstimatedSlippage).HasPrecision(18, 4);
            e.Property(tc => tc.EstimatedOrderType).HasConversion<string>().HasMaxLength(32);
        });

        // PlatformConfig
        mb.Entity<PlatformConfig>(e =>
        {
            e.HasKey(p => p.Id);
            e.Property(p => p.Name).HasMaxLength(128);
            e.Property(p => p.DisplayName).HasMaxLength(256);
            e.Property(p => p.BaseUrl).HasMaxLength(1024);
        });

        // TraderAnalytics
        mb.Entity<TraderAnalytics>(e =>
        {
            e.HasKey(a => a.TraderId);
            e.HasOne(a => a.Trader).WithOne(t => t.Analytics).HasForeignKey<TraderAnalytics>(a => a.TraderId).OnDelete(DeleteBehavior.Cascade);
            e.Property(a => a.OverallPnL).HasPrecision(18, 4);
            e.Property(a => a.OverallWinRate).HasPrecision(8, 4);
            e.Property(a => a.PnL30d).HasPrecision(18, 4);
            e.Property(a => a.WinRate30d).HasPrecision(8, 4);
            e.Property(a => a.PnL7d).HasPrecision(18, 4);
            e.Property(a => a.WinRate7d).HasPrecision(8, 4);
            e.Property(a => a.PnL24h).HasPrecision(18, 4);
            e.Property(a => a.WinRate24h).HasPrecision(8, 4);
            
            // D2c & E3 & E5
            e.Property(a => a.MedianWinReturnPct).HasPrecision(18, 4);
            e.Property(a => a.AvgWinReturnPct).HasPrecision(18, 4);
            e.Property(a => a.MedianLossReturnPct).HasPrecision(18, 4);
            e.Property(a => a.AvgLossReturnPct).HasPrecision(18, 4);
            e.Property(a => a.ProfitFactor).HasPrecision(18, 4);
            
            e.Property(a => a.MedianHoldDurationHours).HasPrecision(18, 4);
            e.Property(a => a.P50PositionSize).HasPrecision(18, 4);
            e.Property(a => a.P90PositionSize).HasPrecision(18, 4);
            e.Property(a => a.TradesPerWeek).HasPrecision(18, 4);
            
            e.Property(a => a.MedianMarketVolumeUsd).HasPrecision(18, 4);
            e.Property(a => a.MedianPostFillDriftPct).HasPrecision(18, 4);
            e.Property(a => a.NetEdgeAfterFeesPct).HasPrecision(18, 4);
        });

        // MarketAnalytics
        mb.Entity<MarketAnalytics>(e =>
        {
            e.HasKey(a => a.MarketId);
            e.Property(a => a.BotActivityScore).HasPrecision(8, 4);
            e.Property(a => a.AverageTradeSize).HasPrecision(18, 4);
        });

        // TraderPosition
        mb.Entity<TraderPosition>(e =>
        {
            e.HasKey(tp => tp.Id);
            e.HasIndex(tp => new { tp.TraderId, tp.MarketOutcomeId }).IsUnique();
            e.Property(tp => tp.SharesHeld).HasPrecision(14, 6);
            e.Property(tp => tp.AvgCost).HasPrecision(10, 6);
            e.Property(tp => tp.RealizedPnl).HasPrecision(18, 4);
            e.HasOne(tp => tp.Trader).WithMany(t => t.Positions).HasForeignKey(tp => tp.TraderId).OnDelete(DeleteBehavior.Cascade);
            e.HasOne(tp => tp.MarketOutcome).WithMany().HasForeignKey(tp => tp.MarketOutcomeId).OnDelete(DeleteBehavior.Cascade);
        });

        // TraderDailySnapshot
        mb.Entity<TraderDailySnapshot>(e =>
        {
            e.HasKey(s => s.Id);
            e.HasIndex(s => new { s.TraderId, s.Date }).IsUnique();
            e.Property(s => s.TotalPnl).HasPrecision(18, 4);
            e.Property(s => s.CurrentBalance).HasPrecision(18, 4);
            e.HasOne(s => s.Trader).WithMany().HasForeignKey(s => s.TraderId).OnDelete(DeleteBehavior.Cascade);
        });

        // MarketOutcomePriceSnapshot
        mb.Entity<MarketOutcomePriceSnapshot>(e =>
        {
            e.HasKey(ps => ps.Id);
            e.HasIndex(ps => new { ps.MarketOutcomeId, ps.Timestamp });
            e.Property(ps => ps.Price).HasPrecision(10, 6);
            e.HasOne(ps => ps.MarketOutcome).WithMany().HasForeignKey(ps => ps.MarketOutcomeId).OnDelete(DeleteBehavior.Cascade);
        });

        // BackgroundJob
        mb.Entity<BackgroundJob>(e =>
        {
            e.HasKey(j => j.Id);
            e.HasIndex(j => j.Status);
            e.HasIndex(j => j.JobType);
            e.Property(j => j.JobType).HasConversion<string>().HasMaxLength(64);
            e.Property(j => j.Status).HasConversion<string>().HasMaxLength(64);
            e.Property(j => j.ErrorMessage).HasMaxLength(4096);
            e.HasOne(j => j.Trader).WithMany().HasForeignKey(j => j.TraderId).OnDelete(DeleteBehavior.SetNull);
        });
    }
}
