import { describe, it, expect, vi } from 'vitest';
import { SecretMigrateService } from '../src/services/secret-migrate.service.js';
import type { Secret, SecretBackend } from '@prisma/client';

function makeSecret(overrides: Partial<Secret>): Secret {
  return {
    id: 'sec',
    name: 'sec',
    backendId: 'b-plain',
    data: {} as Secret['data'],
    externalRef: '',
    version: 1,
    createdAt: new Date(),
    updatedAt: new Date(),
    ...overrides,
  };
}

function makeBackend(overrides: Partial<SecretBackend>): SecretBackend {
  return {
    id: overrides.id ?? 'b',
    name: overrides.name ?? 'b',
    type: overrides.type ?? 'plaintext',
    config: overrides.config ?? ({} as SecretBackend['config']),
    tokenMeta: overrides.tokenMeta ?? ({} as SecretBackend['tokenMeta']),
    isDefault: overrides.isDefault ?? false,
    description: overrides.description ?? '',
    version: 1,
    createdAt: new Date(),
    updatedAt: new Date(),
  };
}

/**
 * Build a service with in-memory state. Mocks the secret repo + backend
 * service enough to exercise the bootstrap-token filter.
 */
function makeService(state: {
  sourceName: string;
  destName: string;
  sourceBackend: SecretBackend;
  destBackend: SecretBackend;
  allBackends: SecretBackend[];
  secretsOnSource: Secret[];
}) {
  const secretRepo = {
    findByBackend: vi.fn(async (backendId: string) => {
      return state.secretsOnSource.filter((s) => s.backendId === backendId);
    }),
    update: vi.fn(async () => ({}) as Secret),
  };
  const backends = {
    list: vi.fn(async () => state.allBackends),
    getByName: vi.fn(async (name: string) => {
      if (name === state.sourceBackend.name) return state.sourceBackend;
      if (name === state.destBackend.name) return state.destBackend;
      throw new Error(`unknown backend: ${name}`);
    }),
    driverFor: vi.fn(() => ({
      read: vi.fn(async () => ({ k: 'v' })),
      write: vi.fn(async () => ({ externalRef: 'ext', storedData: {} })),
      delete: vi.fn(async () => {}),
    })),
  };
  return new SecretMigrateService(
    secretRepo as never,
    backends as never,
  );
}

describe('SecretMigrateService — bootstrap-token protection', () => {
  const sourceBackend = makeBackend({ id: 'b-plain', name: 'default', type: 'plaintext' });
  const destBackend = makeBackend({
    id: 'b-bao',
    name: 'bao',
    type: 'openbao',
    config: { tokenSecretRef: { name: 'bao-creds', key: 'token' } } as unknown as SecretBackend['config'],
  });

  const secrets = [
    makeSecret({ id: 's1', name: 'grafana-creds' }),
    makeSecret({ id: 's2', name: 'bao-creds' }),          // ← bootstrap token for `bao`
    makeSecret({ id: 's3', name: 'ha-creds' }),
  ];

  it('migrate() without --names skips the bootstrap-token secret', async () => {
    const svc = makeService({
      sourceName: 'default',
      destName: 'bao',
      sourceBackend, destBackend,
      allBackends: [sourceBackend, destBackend],
      secretsOnSource: secrets,
    });
    const result = await svc.migrate({ from: 'default', to: 'bao' });

    const migratedNames = result.migrated.map((m) => m.name).sort();
    expect(migratedNames).toEqual(['grafana-creds', 'ha-creds']);

    const skippedBootstrap = result.skipped.find((s) => s.name === 'bao-creds');
    expect(skippedBootstrap, 'bao-creds must be in skipped with a bootstrap-token reason').toBeDefined();
    expect(skippedBootstrap!.reason).toMatch(/bootstrap token/i);
  });

  it('migrate() WITH --names including the bootstrap token still migrates it (explicit override)', async () => {
    const svc = makeService({
      sourceName: 'default',
      destName: 'bao',
      sourceBackend, destBackend,
      allBackends: [sourceBackend, destBackend],
      secretsOnSource: secrets,
    });
    const result = await svc.migrate({ from: 'default', to: 'bao', names: ['bao-creds'] });

    const migratedNames = result.migrated.map((m) => m.name);
    expect(migratedNames).toEqual(['bao-creds']);
    // No bootstrap-skip entry when the user explicitly asked for it
    expect(result.skipped.find((s) => s.reason.includes('bootstrap'))).toBeUndefined();
  });

  it('dryRun() matches migrate() — also filters bootstrap tokens by default', async () => {
    const svc = makeService({
      sourceName: 'default',
      destName: 'bao',
      sourceBackend, destBackend,
      allBackends: [sourceBackend, destBackend],
      secretsOnSource: secrets,
    });
    const candidates = await svc.dryRun({ from: 'default', to: 'bao' });
    const names = candidates.map((c) => c.name).sort();
    expect(names).toEqual(['grafana-creds', 'ha-creds']);
    expect(names).not.toContain('bao-creds');
  });

  it('dryRun() with --names reports exactly the named secrets', async () => {
    const svc = makeService({
      sourceName: 'default',
      destName: 'bao',
      sourceBackend, destBackend,
      allBackends: [sourceBackend, destBackend],
      secretsOnSource: secrets,
    });
    const candidates = await svc.dryRun({ from: 'default', to: 'bao', names: ['bao-creds', 'grafana-creds'] });
    const names = candidates.map((c) => c.name).sort();
    expect(names).toEqual(['bao-creds', 'grafana-creds']);
  });
});
