import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import type { BrowserSessionRepo } from '../db/browser-session-repo.js';
import {
  initMasterKey,
  generateUserDek,
  encryptUserDek,
  encryptStateBlob,
} from '../crypto/sessions.js';
import { loadSessionStateForUser } from './session-loader.js';

const OWNER = 'user-1';
const STATE = { cookies: [{ name: 'sid', value: 'abc' }], origins: [] };

let dir: string;
let masterKeyPath: string;

beforeEach(() => {
  dir = mkdtempSync(join(tmpdir(), 'session-loader-test-'));
  masterKeyPath = join(dir, 'master.key');
});

afterEach(() => {
  rmSync(dir, { recursive: true, force: true });
});

/** Build a stub repo + a valid encrypted blob for OWNER. */
function makeFixture(overrides: {
  profile?: Record<string, unknown> | null;
  encDek?: Buffer | null;
} = {}) {
  const master = initMasterKey(masterKeyPath);
  const dek = generateUserDek();
  const encDek = overrides.encDek !== undefined ? overrides.encDek : encryptUserDek(master, dek);
  const blob = encryptStateBlob(dek, JSON.stringify(STATE));
  const profile =
    overrides.profile !== undefined
      ? overrides.profile
      : { id: 7, ownerId: OWNER, status: 'active', encryptedStateBlob: blob };

  const sessRepo = {
    getProfileById: (id: number, ownerId: string) =>
      profile && id === 7 && ownerId === OWNER ? profile : null,
    getUserDek: (userId: string) => (userId === OWNER ? encDek : null),
  } as unknown as BrowserSessionRepo;

  return { sessRepo, blob };
}

describe('loadSessionStateForUser', () => {
  it('decrypts and parses the storageState for an active profile', async () => {
    const { sessRepo } = makeFixture();
    const res = await loadSessionStateForUser({ sessRepo, masterKeyPath }, OWNER, 7);
    expect(res).toEqual({ ok: true, storageState: STATE });
  });

  it('reports profile_not_found for an unknown id', async () => {
    const { sessRepo } = makeFixture();
    const res = await loadSessionStateForUser({ sessRepo, masterKeyPath }, OWNER, 999);
    expect(res.ok).toBe(false);
    if (!res.ok) expect(res.error.kind).toBe('profile_not_found');
  });

  it('reports profile_not_found when the profile belongs to another user', async () => {
    const { sessRepo } = makeFixture();
    const res = await loadSessionStateForUser({ sessRepo, masterKeyPath }, 'other-user', 7);
    expect(res.ok).toBe(false);
    if (!res.ok) expect(res.error.kind).toBe('profile_not_found');
  });

  it('reports profile_not_active for a non-active status', async () => {
    const base = makeFixture();
    const { sessRepo } = makeFixture({
      profile: { id: 7, ownerId: OWNER, status: 'pending', encryptedStateBlob: base.blob },
    });
    const res = await loadSessionStateForUser({ sessRepo, masterKeyPath }, OWNER, 7);
    expect(res.ok).toBe(false);
    if (!res.ok) {
      expect(res.error.kind).toBe('profile_not_active');
      expect(res.error.message).toContain('status=pending');
    }
  });

  it('reports profile_not_active when the blob is missing', async () => {
    const { sessRepo } = makeFixture({
      profile: { id: 7, ownerId: OWNER, status: 'active', encryptedStateBlob: null },
    });
    const res = await loadSessionStateForUser({ sessRepo, masterKeyPath }, OWNER, 7);
    expect(res.ok).toBe(false);
    if (!res.ok) expect(res.error.kind).toBe('profile_not_active');
  });

  it('reports dek_not_found when the user has no stored DEK', async () => {
    const { sessRepo } = makeFixture({ encDek: null });
    const res = await loadSessionStateForUser({ sessRepo, masterKeyPath }, OWNER, 7);
    expect(res.ok).toBe(false);
    if (!res.ok) expect(res.error.kind).toBe('dek_not_found');
  });

  it('reports decrypt_error for a corrupted blob and never throws', async () => {
    const { sessRepo } = makeFixture({
      profile: { id: 7, ownerId: OWNER, status: 'active', encryptedStateBlob: Buffer.from('garbage') },
    });
    const res = await loadSessionStateForUser({ sessRepo, masterKeyPath }, OWNER, 7);
    expect(res.ok).toBe(false);
    if (!res.ok) expect(res.error.kind).toBe('decrypt_error');
  });
});
