import { describe, expect, it } from 'vitest'
import { decideEfcorePolicy, isCompliantMigrationName, scanDestructiveOps } from '../efcore.js'
import type { EfCoreValidation } from '../types.js'

function v(partial: Partial<EfCoreValidation>): EfCoreValidation {
  return {
    detected: true,
    valid: true,
    destructive: false,
    migrations: [],
    missing: [],
    destructiveOps: [],
    ...partial,
  }
}

describe('decideEfcorePolicy (restored 4.x semantics)', () => {
  it('no migrations detected → no block, no confirmation, no warnings', () => {
    const p = decideEfcorePolicy(v({ detected: false }), { enabled: true, validateOnCommit: true, blockDestructive: true })
    expect(p.block).toBe(false)
    expect(p.requiresConfirmation).toBe(false)
    expect(p.warnings).toHaveLength(0)
  })

  it('destructive + blockDestructive → requiresConfirmation, NOT a silent hard block', () => {
    const p = decideEfcorePolicy(
      v({ destructive: true, destructiveOps: ['DropColumn'] }),
      { enabled: true, validateOnCommit: true, blockDestructive: true },
    )
    expect(p.block).toBe(false)
    expect(p.requiresConfirmation).toBe(true)
  })

  it('destructive + blockDestructive:false → warning only', () => {
    const p = decideEfcorePolicy(
      v({ destructive: true, destructiveOps: ['DropColumn'] }),
      { enabled: true, validateOnCommit: true, blockDestructive: false },
    )
    expect(p.block).toBe(false)
    expect(p.requiresConfirmation).toBe(false)
    expect(p.warnings.join(' ')).toMatch(/destructive/i)
  })

  it('incomplete migration + validateOnCommit → hard block', () => {
    const p = decideEfcorePolicy(
      v({ valid: false, missing: ['ModelSnapshot.cs'] }),
      { enabled: true, validateOnCommit: true, blockDestructive: true },
    )
    expect(p.block).toBe(true)
  })

  it('incomplete migration + validateOnCommit:false → warning only', () => {
    const p = decideEfcorePolicy(
      v({ valid: false, missing: ['ModelSnapshot.cs'] }),
      { enabled: true, validateOnCommit: false, blockDestructive: true },
    )
    expect(p.block).toBe(false)
    expect(p.warnings.join(' ')).toMatch(/incomplete/i)
  })

  it('enabled:false → warn-only regardless of the other flags', () => {
    const p = decideEfcorePolicy(
      v({ destructive: true, valid: false, destructiveOps: ['DropTable'], missing: ['ModelSnapshot.cs'] }),
      { enabled: false, validateOnCommit: true, blockDestructive: true },
    )
    expect(p.block).toBe(false)
    expect(p.requiresConfirmation).toBe(false)
    expect(p.warnings.length).toBeGreaterThan(0)
  })
})

describe('scanDestructiveOps (Up-only — Down() rollback excluded)', () => {
  // A plain InitialCreate: every table CREATEd in Up() has its inverse
  // DropTable in the auto-scaffolded Down(). The Down() drops must NOT count.
  const INITIAL_CREATE = `
public partial class core_v3_55_0_001_InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "auth_Groups",
            schema: "core",
            columns: table => new { Id = table.Column<Guid>(nullable: false) });
        migrationBuilder.InsertData(schema: "core", table: "auth_Groups", columns: new[] { "Id" }, values: new object[] { "x" });
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "auth_Groups", schema: "core");
    }
}`

  it('InitialCreate (CreateTable in Up, DropTable only in Down) → no destructive ops', () => {
    expect(scanDestructiveOps(INITIAL_CREATE)).toEqual([])
  })

  it('genuine forward DropColumn in Up() IS still detected (true positive preserved)', () => {
    const m = `
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropColumn(name: "Obsolete", table: "ext_Orders");
    }
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AddColumn<string>(name: "Obsolete", table: "ext_Orders");
    }`
    expect(scanDestructiveOps(m)).toEqual(['DropColumn'])
  })

  it('forward op is caught while the SAME op appearing only in Down() is not double-counted', () => {
    const m = `
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DeleteData(table: "ext_Seed", keyColumn: "Id", keyValue: 1);
    }
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "ext_Old");
    }`
    expect(scanDestructiveOps(m)).toEqual(['DeleteData'])
  })

  it('migration with no Down() → whole body scanned (fail-open)', () => {
    const m = `
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "ext_Legacy");
    }`
    expect(scanDestructiveOps(m)).toEqual(['DropTable'])
  })

  it('ignores a destructive WORD inside an inlined frozen SQL object — only a real .Op( call counts', () => {
    // /efcore now inlines SQL objects as migrationBuilder.Sql(@"…") literals in
    // Up(). A view/proc name or comment containing "DropTable"/"DeleteData" must
    // NOT be read as a C# destructive call.
    const m = `
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(name: "ext_Logs", schema: "extensions");
        migrationBuilder.Sql(@"CREATE VIEW extensions.vw_DropTableAudit AS SELECT 1 AS X; -- DeleteData archive note");
    }
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "ext_Logs", schema: "extensions");
        migrationBuilder.Sql(@"DROP VIEW extensions.vw_DropTableAudit;");
    }`
    expect(scanDestructiveOps(m)).toEqual([])
  })

  it('does not mistake Update(/Download( for Up(/Down(', () => {
    // No real Up()/Down() signatures — fail-open scans all; only the genuine op counts.
    const m = `// migrationBuilder helpers: Update(), Download()  — no drops here`
    expect(scanDestructiveOps(m)).toEqual([])
  })
})

describe('isCompliantMigrationName (4.x naming preflight)', () => {
  it('accepts {context}_v{version}_{seq3}_{Description}, dot or underscore version, optional EF timestamp', () => {
    expect(isCompliantMigrationName('20260128220215_core_v2.0.0_001_InitialSchema.cs')).toBe(true)
    expect(isCompliantMigrationName('ext_v1_0_0_003_CreateOrders')).toBe(true)
  })
  it('rejects non-conventional names', () => {
    expect(isCompliantMigrationName('20260128220215_AddApplicationAccessRequests.cs')).toBe(false)
    expect(isCompliantMigrationName('InitialCreate')).toBe(false)
  })
})
