import express, { Router } from 'express';
import { canManageSpace } from './visibility.js';
import { viewerOf } from './space-viewer.js';
import type { SpaceInvite, SpaceInviteRole } from '../db/repository.js';
import type { SpaceApiDeps } from './space-api.js';

/**
 * 招待リンク（再利用トークン）。createSpaceApi から分離（巨大関数分割）。挙動は等価。
 *
 * 組織非依存の招待経路。発行/無効化は canManageSpace、参加は認証済みユーザー。
 * no-auth モードでは他ユーザーの概念が無いので invite 系は一律 404。
 * spec: docs/superpowers/specs/2026-06-19-space-invite-links-design.md
 */
export function registerSpaceInviteRoutes(router: Router, deps: SpaceApiDeps): void {
  const { repo } = deps;
  const jsonParser = express.json({ limit: deps.uploadLimitMb ? `${deps.uploadLimitMb}mb` : '50mb' });

  const INVITE_ROLES: readonly SpaceInviteRole[] = ['editor', 'viewer'];
  const isInviteRole = (r: unknown): r is SpaceInviteRole =>
    typeof r === 'string' && (INVITE_ROLES as readonly string[]).includes(r);

  // invite を API レスポンス形に整える（url は相対パス。SPA が origin を前置する）。
  const inviteOut = (inv: SpaceInvite) => ({
    token: inv.token,
    url: `/ui/invite/${inv.token}`,
    role: inv.role,
    createdAt: inv.createdAt,
    expiresAt: inv.expiresAt,
    revokedAt: inv.revokedAt,
    valid: repo.isSpaceInviteValid(inv),
  });

  // 現行リンクを取得（canManageSpace）。無ければ { invite: null }。
  router.get('/:id/invite', async (req, res) => {
    if (!deps.authActive) return res.status(404).json({ error: 'not found' });
    const viewer = viewerOf(req, deps.authActive);
    const space = await repo.getSpace(req.params.id, { viewer });
    if (!space) return res.status(404).json({ error: 'not found' });
    const memberRole = repo.getSpaceMemberRole(space.id, viewer.id);
    if (!canManageSpace(viewer, { ownerId: space.ownerId }, memberRole)) {
      return res.status(403).json({ error: 'forbidden' });
    }
    const inv = repo.getSpaceInvite(space.id);
    res.json({ invite: inv ? inviteOut(inv) : null });
  });

  // リンクを (再)生成（canManageSpace）。body { role, expiresInDays? }。
  router.post('/:id/invite', jsonParser, async (req, res) => {
    if (!deps.authActive) return res.status(404).json({ error: 'not found' });
    const viewer = viewerOf(req, deps.authActive);
    const space = await repo.getSpace(req.params.id, { viewer });
    if (!space) return res.status(404).json({ error: 'not found' });
    const memberRole = repo.getSpaceMemberRole(space.id, viewer.id);
    if (!canManageSpace(viewer, { ownerId: space.ownerId }, memberRole)) {
      return res.status(403).json({ error: 'forbidden' });
    }
    const role = req.body?.role ?? 'viewer';
    if (!isInviteRole(role)) return res.status(400).json({ error: 'invalid role' });
    const rawDays = req.body?.expiresInDays;
    let expiresInDays: number | null = null;
    if (rawDays != null) {
      const n = Number(rawDays);
      if (!Number.isInteger(n) || n <= 0) {
        return res.status(400).json({ error: 'expiresInDays must be a positive integer' });
      }
      expiresInDays = n;
    }
    const inv = repo.createSpaceInvite({ spaceId: space.id, role, createdBy: viewer.id, expiresInDays });
    res.status(201).json({ invite: inviteOut(inv) });
  });

  // リンクを無効化（canManageSpace）。
  router.delete('/:id/invite', async (req, res) => {
    if (!deps.authActive) return res.status(404).json({ error: 'not found' });
    const viewer = viewerOf(req, deps.authActive);
    const space = await repo.getSpace(req.params.id, { viewer });
    if (!space) return res.status(404).json({ error: 'not found' });
    const memberRole = repo.getSpaceMemberRole(space.id, viewer.id);
    if (!canManageSpace(viewer, { ownerId: space.ownerId }, memberRole)) {
      return res.status(403).json({ error: 'forbidden' });
    }
    repo.revokeSpaceInvite(space.id);
    res.status(204).end();
  });

  // プレビュー（認証済み・メンバーでなくてよい）。無効/不明トークンは 404 で、
  // スペース情報を一切返さない（列挙・情報漏洩対策）。
  // ルーティング: '/invite/:token' は1セグメント目が literal 'invite'。'/:id/invite'
  // とは排他（スペース id は UUID で 'invite' と衝突しない）。
  router.get('/invite/:token', async (req, res) => {
    if (!deps.authActive) return res.status(404).json({ error: 'not found' });
    const inv = repo.getSpaceInviteByToken(req.params.token);
    if (!repo.isSpaceInviteValid(inv)) return res.status(404).json({ error: 'invalid invite' });
    const space = await repo.getSpace(inv.spaceId, { viewer: { id: 'x', role: 'admin', orgIds: [] } as unknown as Express.User });
    if (!space) return res.status(404).json({ error: 'invalid invite' });
    res.json({ spaceId: space.id, spaceTitle: space.title, role: inv.role });
  });

  // 参加（認証済み）。検証 → メンバー追加（冪等）。既メンバー/オーナーも 200。
  router.post('/invite/:token/accept', async (req, res) => {
    if (!deps.authActive) return res.status(404).json({ error: 'not found' });
    const viewer = viewerOf(req, deps.authActive);
    const inv = repo.getSpaceInviteByToken(req.params.token);
    if (!repo.isSpaceInviteValid(inv)) return res.status(404).json({ error: 'invalid invite' });
    const space = await repo.getSpace(inv.spaceId, { viewer: { id: 'x', role: 'admin', orgIds: [] } as unknown as Express.User });
    if (!space) return res.status(404).json({ error: 'invalid invite' });
    // オーナー本人 → 既に最上位権限。メンバー行は作らず成功扱い。
    if (space.ownerId && viewer.id === space.ownerId) {
      return res.json({ spaceId: space.id, alreadyMember: true });
    }
    const existing = repo.getSpaceMemberRole(space.id, viewer.id);
    if (existing) return res.json({ spaceId: space.id, alreadyMember: true });
    await repo.addSpaceMember({ spaceId: space.id, userId: viewer.id, role: inv.role, invitedBy: inv.createdBy });
    res.json({ spaceId: space.id, alreadyMember: false });
  });
}
