using System.Data.Common;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Seeding;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace ClaudeDo.Data;

public class ClaudeDoDbContext : DbContext
{
    // Runs PRAGMA foreign_keys=ON on every EF-managed connection open so FK
    // enforcement is active for all IDbContextFactory-created contexts, not
    // just the single context used in MigrateAndConfigure.
    private sealed class SqliteForeignKeyInterceptor : DbConnectionInterceptor
    {
        internal static readonly SqliteForeignKeyInterceptor Instance = new();

        public override void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData)
            => Apply(connection);

        public override Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData eventData, CancellationToken cancellationToken = default)
        {
            Apply(connection);
            return Task.CompletedTask;
        }

        private static void Apply(DbConnection connection)
        {
            using var cmd = connection.CreateCommand();
            cmd.CommandText = "PRAGMA foreign_keys=ON;";
            cmd.ExecuteNonQuery();
        }
    }

    public ClaudeDoDbContext(DbContextOptions<ClaudeDoDbContext> options) : base(options) { }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.AddInterceptors(SqliteForeignKeyInterceptor.Instance);

    public DbSet<TaskEntity> Tasks => Set<TaskEntity>();
    public DbSet<ListEntity> Lists => Set<ListEntity>();
    public DbSet<ListConfigEntity> ListConfigs => Set<ListConfigEntity>();
    public DbSet<WorktreeEntity> Worktrees => Set<WorktreeEntity>();
    public DbSet<TaskRunEntity> TaskRuns => Set<TaskRunEntity>();
    public DbSet<SubtaskEntity> Subtasks => Set<SubtaskEntity>();
    public DbSet<TaskAttachmentEntity> TaskAttachments => Set<TaskAttachmentEntity>();
    public DbSet<AppSettingsEntity> AppSettings => Set<AppSettingsEntity>();
    public DbSet<PrimeScheduleEntity> PrimeSchedules => Set<PrimeScheduleEntity>();
    public DbSet<DailyNoteEntity> DailyNotes => Set<DailyNoteEntity>();
    public DbSet<WeekReportEntity> WeekReports => Set<WeekReportEntity>();
    public DbSet<SessionSkillEntity> SessionSkills => Set<SessionSkillEntity>();

    private static readonly ValueConverter<DateTime, DateTime> UtcConverter =
        new(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

    private static readonly ValueConverter<DateTime?, DateTime?> UtcNullableConverter =
        new(v => v, v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : null);

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(ClaudeDoDbContext).Assembly);

        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        foreach (var property in entityType.GetProperties())
        {
            if (property.ClrType == typeof(DateTime) && property.GetValueConverter() == null)
                property.SetValueConverter(UtcConverter);
            else if (property.ClrType == typeof(DateTime?) && property.GetValueConverter() == null)
                property.SetValueConverter(UtcNullableConverter);
        }
    }

    // The 40 incremental migrations were squashed into a single InitialCreate on 2026-08-26.
    // Any database that already carries the full pre-squash schema has to be re-stamped to the
    // squashed id, or Migrate() would run CREATE TABLE over live tables.
    private const string SquashedMigrationId = "20260826094154_InitialCreate";
    private const string LastPreSquashMigrationId = "20260825063230_AddPrimeActionKind";

    /// <summary>
    /// Applies EF Core migrations and sets WAL mode. Safe for both fresh and existing databases.
    /// Existing databases are baselined onto <see cref="SquashedMigrationId"/> instead of being
    /// re-created: ones with tables but no __EFMigrationsHistory (created by the old schema.sql),
    /// and ones whose history still lists the pre-squash chain. A database that stopped *mid*-chain
    /// can no longer be upgraded — the incremental migrations are gone — and throws instead of
    /// being silently stamped onto a schema it doesn't have.
    /// </summary>
    public static void MigrateAndConfigure(ClaudeDoDbContext db)
    {
        var conn = db.Database.GetDbConnection();
        try
        {
            conn.Open();

            // Set WAL FIRST, before migrations — prevents write-lock contention
            // when UI and Worker start simultaneously.
            using (var walCmd = conn.CreateCommand())
            {
                walCmd.CommandText = "PRAGMA journal_mode=wal;";
                walCmd.ExecuteNonQuery();
            }

            // Enable FK enforcement — SQLite defaults to OFF per connection.
            using (var fkCmd = conn.CreateCommand())
            {
                fkCmd.CommandText = "PRAGMA foreign_keys=ON;";
                fkCmd.ExecuteNonQuery();
            }

            using (var cmd = conn.CreateCommand())
            {
                cmd.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='lists'";
                var hasLists = Convert.ToInt64(cmd.ExecuteScalar()) > 0;

                cmd.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='__EFMigrationsHistory'";
                var hasHistory = Convert.ToInt64(cmd.ExecuteScalar()) > 0;

                if (hasLists && !hasHistory)
                {
                    // Pre-EF database: tables, no history at all.
                    cmd.CommandText = $"""
                        CREATE TABLE "__EFMigrationsHistory" (
                            "MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
                            "ProductVersion" TEXT NOT NULL
                        );
                        INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
                        VALUES ('{SquashedMigrationId}', '8.0.11');
                        """;
                    cmd.ExecuteNonQuery();
                }
                else if (hasLists && hasHistory)
                {
                    cmd.CommandText = $"""SELECT COUNT(*) FROM "__EFMigrationsHistory" WHERE "MigrationId" = '{SquashedMigrationId}'""";
                    var alreadySquashed = Convert.ToInt64(cmd.ExecuteScalar()) > 0;

                    if (!alreadySquashed)
                    {
                        cmd.CommandText = """SELECT MAX("MigrationId") FROM "__EFMigrationsHistory" """;
                        var newest = cmd.ExecuteScalar() as string ?? "";

                        if (newest != LastPreSquashMigrationId)
                            throw new InvalidOperationException(
                                $"This database was last migrated by '{newest}', but the migration history was " +
                                $"squashed into '{SquashedMigrationId}' and the incremental migrations no longer " +
                                "exist. Reinstall ClaudeDo against a fresh database (or restore a backup taken " +
                                $"after '{LastPreSquashMigrationId}').");

                        cmd.CommandText = $"""
                            DELETE FROM "__EFMigrationsHistory";
                            INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
                            VALUES ('{SquashedMigrationId}', '8.0.11');
                            """;
                        cmd.ExecuteNonQuery();
                    }
                }
            }
        }
        finally
        {
            conn.Close();
        }

        db.Database.Migrate();
        DefaultListsSeeder.SeedAsync(db).GetAwaiter().GetResult();
    }
}
