import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { randomUUID } from 'node:crypto';
import { Repository } from './repository.js';

describe('app_share_links repository methods', () => {
  let repo: Repository;
  let dir: string;
  let spaceId: string;

  beforeEach(async () => {
    dir = mkdtempSync(join(tmpdir(), 'app-share-repo-'));
    repo = new Repository(join(dir, `${randomUUID()}.db`));
    // 本物の space を 1 件作る（FK 制約 spaces(id) を満たすため）。
    const space = await repo.createSpace({ kind: 'case', title: '案件A', ownerId: 'user-1' });
    spaceId = space.id;
  });

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

  it('createAppShareLink → resolveAppShareToken で往復できる', () => {
    const { token } = repo.createAppShareLink(spaceId, 'dashboard', 'user-1');
    expect(typeof token).toBe('string');
    expect(token.length).toBeGreaterThan(0);
    const resolved = repo.resolveAppShareToken(token);
    expect(resolved).toEqual({ spaceId, appName: 'dashboard' });
  });

  it('未失効の重複作成は同一トークンを再利用する', () => {
    const first = repo.createAppShareLink(spaceId, 'dashboard', 'user-1');
    const second = repo.createAppShareLink(spaceId, 'dashboard', 'user-1');
    expect(second.token).toBe(first.token);
  });

  it('別アプリは別トークンを発行する', () => {
    const a = repo.createAppShareLink(spaceId, 'dashboard', 'user-1');
    const b = repo.createAppShareLink(spaceId, 'reporter', 'user-1');
    expect(b.token).not.toBe(a.token);
  });

  it('getAppShareLink は現行リンクの状態を返す', () => {
    expect(repo.getAppShareLink(spaceId, 'dashboard')).toBeNull();
    const { token } = repo.createAppShareLink(spaceId, 'dashboard', 'user-1');
    const got = repo.getAppShareLink(spaceId, 'dashboard');
    expect(got).not.toBeNull();
    expect(got!.token).toBe(token);
    expect(got!.revokedAt).toBeNull();
  });

  it('revokeAppShareLink 後は resolveAppShareToken が null を返す', () => {
    const { token } = repo.createAppShareLink(spaceId, 'dashboard', 'user-1');
    repo.revokeAppShareLink(spaceId, 'dashboard');
    expect(repo.resolveAppShareToken(token)).toBeNull();
  });

  it('revoke 後の getAppShareLink は revokedAt を持つ', () => {
    repo.createAppShareLink(spaceId, 'dashboard', 'user-1');
    repo.revokeAppShareLink(spaceId, 'dashboard');
    const got = repo.getAppShareLink(spaceId, 'dashboard');
    expect(got).not.toBeNull();
    expect(got!.revokedAt).not.toBeNull();
  });

  it('失効後の再作成は新しいトークンを発行する（古いトークンは再利用しない）', () => {
    const first = repo.createAppShareLink(spaceId, 'dashboard', 'user-1');
    repo.revokeAppShareLink(spaceId, 'dashboard');
    const second = repo.createAppShareLink(spaceId, 'dashboard', 'user-1');
    expect(second.token).not.toBe(first.token);
    // 古いトークンは失効済みのまま resolve 不可
    expect(repo.resolveAppShareToken(first.token)).toBeNull();
    // 新トークンは有効
    expect(repo.resolveAppShareToken(second.token)).toEqual({ spaceId, appName: 'dashboard' });
  });

  it('不正トークンは resolveAppShareToken が null を返す', () => {
    expect(repo.resolveAppShareToken('nonexistent')).toBeNull();
  });

  it('createdBy が null でも作成できる', () => {
    const { token } = repo.createAppShareLink(spaceId, 'dashboard', null);
    expect(repo.resolveAppShareToken(token)).toEqual({ spaceId, appName: 'dashboard' });
  });
});
