// Repository レベル: 招待リンクの有効性判定と「1スペース1本」不変条件。
// spec: docs/superpowers/specs/2026-06-19-space-invite-links-design.md

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

describe('Repository space_invites', () => {
  let dir = '';
  let repo: Repository;
  let spaceId = '';
  let ownerId = '';

  beforeEach(async () => {
    dir = mkdtempSync(join(tmpdir(), 'repo-invite-'));
    repo = new Repository(join(dir, 'db.sqlite'));
    ownerId = repo.createUser({ email: 'o@x.com', name: 'o', role: 'user', status: 'active' }).id;
    const space = await repo.createSpace({ kind: 'case', title: 'S', ownerId, visibility: 'private' });
    spaceId = space.id;
  });

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

  it('createSpaceInvite returns a valid, no-expiry invite by default', () => {
    const inv = repo.createSpaceInvite({ spaceId, role: 'viewer', createdBy: ownerId });
    expect(inv.token).toBeTruthy();
    expect(inv.role).toBe('viewer');
    expect(inv.expiresAt).toBeNull();
    expect(inv.revokedAt).toBeNull();
    expect(repo.isSpaceInviteValid(inv)).toBe(true);
  });

  it('keeps only one invite per space (regenerate deletes the prior row)', () => {
    const a = repo.createSpaceInvite({ spaceId, role: 'viewer', createdBy: ownerId });
    const b = repo.createSpaceInvite({ spaceId, role: 'editor', createdBy: ownerId });
    expect(b.token).not.toBe(a.token);
    expect(repo.getSpaceInviteByToken(a.token)).toBeNull(); // old row gone
    expect(repo.getSpaceInvite(spaceId)?.token).toBe(b.token);
  });

  it('a future expiry is valid; a past expiry is invalid', () => {
    const future = repo.createSpaceInvite({ spaceId, role: 'viewer', expiresInDays: 7 });
    expect(future.expiresAt).toBeTruthy();
    expect(repo.isSpaceInviteValid(future)).toBe(true);
    // a hand-built invite with a past expiry must be rejected by the validity check
    expect(
      repo.isSpaceInviteValid({ ...future, expiresAt: '2000-01-01 00:00:00' }),
    ).toBe(false);
  });

  it('revokeSpaceInvite invalidates the current link', () => {
    const inv = repo.createSpaceInvite({ spaceId, role: 'viewer' });
    repo.revokeSpaceInvite(spaceId);
    const after = repo.getSpaceInviteByToken(inv.token)!;
    expect(after.revokedAt).toBeTruthy();
    expect(repo.isSpaceInviteValid(after)).toBe(false);
  });

  it('isSpaceInviteValid is false for null/undefined', () => {
    expect(repo.isSpaceInviteValid(null)).toBe(false);
    expect(repo.isSpaceInviteValid(undefined)).toBe(false);
  });

  it('deletes invites when the space is hard-deleted (FK cascade)', async () => {
    const inv = repo.createSpaceInvite({ spaceId, role: 'viewer' });
    await repo.hardDeleteSpace(spaceId);
    expect(repo.getSpaceInviteByToken(inv.token)).toBeNull();
  });
});
