import { type MigrationInterface, type QueryRunner } from 'typeorm';

/**
 * password_resets — single-use, 30-minute, hashed reset tokens.
 *
 * Per security.md A07 + A09:
 *   - Token is generated as a 32-byte random URL-safe string and
 *     SHA-256 hashed before persisting. The raw token is only ever
 *     held in memory long enough to email it; lookups happen by
 *     hash so a leaked DB row can't be redeemed.
 *   - Single-use → `used_at` is null until the user successfully
 *     resets the password; subsequent attempts with the same token
 *     are rejected.
 *   - 30-minute expiry → `expires_at` enforced at lookup time.
 *   - `request_ip` + `request_user_agent` captured for audit / abuse
 *     detection per A09.
 *
 * Row is tenant-scoped via user_id → users.tenant_id; the column is
 * denormalised here too so cross-tenant token redemption is
 * impossible even on a raw SQL bug.
 */
export class PasswordResets1778000000000 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE IF NOT EXISTS password_resets (
        id           uuid        PRIMARY KEY DEFAULT gen_random_uuid(),
        tenant_id    uuid        NOT NULL,
        user_id      uuid        NOT NULL,
        token_hash   varchar(64) NOT NULL,
        expires_at   timestamptz NOT NULL,
        used_at      timestamptz NULL,
        request_ip   varchar(45) NULL,
        request_user_agent text  NULL,
        created_at   timestamptz NOT NULL DEFAULT now(),
        is_deleted   boolean     NOT NULL DEFAULT false
      )
    `);
    // Lookup is by token_hash. Unique because the hash is 256 bits —
    // collisions practically impossible, and we want a fast key.
    await queryRunner.query(
      `CREATE UNIQUE INDEX IF NOT EXISTS IDX_password_resets_token_hash ON password_resets (token_hash) WHERE is_deleted = false`,
    );
    // Defensive: rapid scans of "still-redeemable rows for this user"
    // when invalidating prior tokens on a new request.
    await queryRunner.query(
      `CREATE INDEX IF NOT EXISTS IDX_password_resets_user_active ON password_resets (user_id) WHERE used_at IS NULL AND is_deleted = false`,
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP INDEX IF EXISTS IDX_password_resets_user_active`);
    await queryRunner.query(`DROP INDEX IF EXISTS IDX_password_resets_token_hash`);
    await queryRunner.query(`DROP TABLE IF EXISTS password_resets`);
  }
}
