import { describe, expect, it } from 'vitest';
import {
  classifyDbTarget,
  decideMigrationAction,
  hasDestructiveUp,
  type MigrationActionInput,
} from '../migration-policy.js';

describe('classifyDbTarget — allowlist + fail-closed', () => {
  it('classifies well-known local hosts as local', () => {
    for (const cs of [
      'Server=(localdb)\\MSSQLLocalDB;Database=App;Trusted_Connection=True',
      'Server=localhost;Database=App',
      'Data Source=127.0.0.1,1433;Initial Catalog=App',
      'Server=.;Database=App',
      'Server=.\\SQLEXPRESS;Database=App',
      'Server=(local);Database=App',
      'Server=localhost,14330;Database=App', // docker-mapped local port
    ]) {
      expect(classifyDbTarget(cs).target, cs).toBe('local');
    }
  });

  it('classifies Azure / FQDN / non-loopback IP as remote', () => {
    expect(classifyDbTarget('Server=tcp:myapp.database.windows.net,1433;Database=App').target).toBe('remote');
    expect(classifyDbTarget('Server=db.prod.internal;Database=App').target).toBe('remote');
    expect(classifyDbTarget('Server=10.0.0.5;Database=App').target).toBe('remote');
  });

  it('a bare machine name cannot be PROVEN local → unknown (fail-closed)', () => {
    expect(classifyDbTarget('Server=MYPC\\SQLEXPRESS;Database=App').target).toBe('unknown');
  });

  it('missing / empty connection string → unknown', () => {
    expect(classifyDbTarget(null).target).toBe('unknown');
    expect(classifyDbTarget('').target).toBe('unknown');
    expect(classifyDbTarget('Database=App;Trusted_Connection=True').target).toBe('unknown'); // no Server token
  });
});

describe('decideMigrationAction — create (history + destructive axes)', () => {
  const create = (branch: string, destructive = false): MigrationActionInput => ({ op: 'create', branch, destructive });

  it('🔴 refuses on a protected production branch', () => {
    const v = decideMigrationAction(create('main'));
    expect(v.tier).toBe('red');
    expect(v.blocked).toBe(true);
    expect(v.axis).toBe('branch');
  });

  it('🟡 confirms on release / hotfix (unusual, ships fast to prod)', () => {
    expect(decideMigrationAction(create('release/5.1.0')).tier).toBe('yellow');
    expect(decideMigrationAction(create('hotfix/urgent')).tier).toBe('yellow');
  });

  it('🟢 autonomous for an additive migration on feature / develop', () => {
    expect(decideMigrationAction(create('feature/x')).tier).toBe('green');
    expect(decideMigrationAction(create('develop')).tier).toBe('green');
  });

  it('🟡 a data-losing Up() on a feature branch needs confirmation', () => {
    const v = decideMigrationAction(create('feature/x', true));
    expect(v.tier).toBe('yellow');
    expect(v.requiresConfirmation).toBe(true);
    expect(v.axis).toBe('destructive');
  });
});

describe('decideMigrationAction — apply / revert (db-target axis)', () => {
  it('🟢 apply to a LOCAL dev DB is autonomous (branch-independent)', () => {
    expect(decideMigrationAction({ op: 'apply', branch: 'feature/x', dbTarget: 'local' }).tier).toBe('green');
    expect(decideMigrationAction({ op: 'apply', branch: 'develop', dbTarget: 'local' }).tier).toBe('green');
  });

  it('🔴 apply to a remote OR unknown DB is refused', () => {
    expect(decideMigrationAction({ op: 'apply', branch: 'feature/x', dbTarget: 'remote' }).tier).toBe('red');
    expect(decideMigrationAction({ op: 'apply', branch: 'feature/x', dbTarget: 'unknown' }).tier).toBe('red');
    expect(decideMigrationAction({ op: 'apply', branch: 'feature/x' }).tier).toBe('red'); // missing → unknown → red
  });

  it('revert: 🟡 on local, 🔴 on non-local', () => {
    expect(decideMigrationAction({ op: 'revert', branch: 'feature/x', dbTarget: 'local' }).tier).toBe('yellow');
    expect(decideMigrationAction({ op: 'revert', branch: 'feature/x', dbTarget: 'remote' }).tier).toBe('red');
  });
});

describe('decideMigrationAction — remove / squash', () => {
  it('remove: 🟡 on a feature branch, 🔴 on main', () => {
    expect(decideMigrationAction({ op: 'remove', branch: 'feature/x' }).tier).toBe('yellow');
    expect(decideMigrationAction({ op: 'remove', branch: 'main' }).tier).toBe('red');
  });

  it('squash is always 🔴 here (delegated to the dedicated squash policy)', () => {
    expect(decideMigrationAction({ op: 'squash', branch: 'feature/x' }).tier).toBe('red');
  });
});

describe('hasDestructiveUp — Up()-body scan', () => {
  it('flags data-losing builder calls', () => {
    expect(hasDestructiveUp('migrationBuilder.DropColumn(name: "X", table: "T");')).toBe(true);
    expect(hasDestructiveUp('migrationBuilder.DropTable(name: "T");')).toBe(true);
    expect(hasDestructiveUp('migrationBuilder.DropForeignKey(...);')).toBe(true);
  });

  it('does not flag additive / index-only operations', () => {
    expect(hasDestructiveUp('migrationBuilder.AddColumn<string>(name: "X", table: "T");')).toBe(false);
    expect(hasDestructiveUp('migrationBuilder.CreateTable(...);')).toBe(false);
    expect(hasDestructiveUp('migrationBuilder.DropIndex(name: "IX_T");')).toBe(false); // no data loss
    expect(hasDestructiveUp(null)).toBe(false);
  });

  it('a frozen SQL literal that merely mentions DROP is not a builder call', () => {
    expect(hasDestructiveUp('migrationBuilder.Sql(@"-- DropColumn is described here");')).toBe(false);
  });
});
