import type { FastifyInstance } from 'fastify';
import type {
  AddRepoRequest,
  CreateGroupRequest,
  CreateGroupSessionRequest,
  GroupResponse,
  GroupSessionResponse,
  GroupsListResponse,
  UpdateGroupRequest,
} from '@arboretum/shared';
import type { GroupManager } from '../core/group-manager.js';
import type { WorktreeManager } from '../core/worktree-manager.js';
import type { PtyManager } from '../core/pty-manager.js';
import type { Db } from '../db/index.js';
import { recordAudit } from '../core/audit-log.js';
import { resolveGroupCwd } from '../core/group-session.js';
import { sendManagerError } from './repos.js';

export function registerGroupRoutes(
  app: FastifyInstance,
  gm: GroupManager,
  db: Db,
  wt: WorktreeManager,
  manager: PtyManager,
): void {
  app.get('/api/v1/groups', async (): Promise<GroupsListResponse> => ({ groups: gm.listGroups() }));

  app.get('/api/v1/groups/:id', async (req, reply) => {
    const { id } = req.params as { id: string };
    try {
      return reply.send({ group: gm.getGroup(id) } satisfies GroupResponse);
    } catch (err) {
      return sendManagerError(reply, err);
    }
  });

  app.post('/api/v1/groups', async (req, reply) => {
    const body = req.body as Partial<CreateGroupRequest> | null;
    if (!body || typeof body.label !== 'string') {
      return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'label is required' } });
    }
    try {
      const group = gm.createGroup({
        label: body.label,
        ...(body.description !== undefined ? { description: body.description } : {}),
        ...(body.color !== undefined ? { color: body.color } : {}),
        ...(Array.isArray(body.repoIds) ? { repoIds: body.repoIds } : {}),
      });
      recordAudit(db, { actor: req.authContext?.tokenId ?? 'unknown', action: 'group.create', resourceId: group.id, details: { label: group.label } });
      return reply.status(201).send({ group } satisfies GroupResponse);
    } catch (err) {
      return sendManagerError(reply, err);
    }
  });

  app.patch('/api/v1/groups/:id', async (req, reply) => {
    const { id } = req.params as { id: string };
    const body = (req.body as Partial<UpdateGroupRequest> | null) ?? {};
    try {
      const group = gm.updateGroup(id, {
        ...(body.label !== undefined ? { label: body.label } : {}),
        ...(body.description !== undefined ? { description: body.description } : {}),
        ...(body.color !== undefined ? { color: body.color } : {}),
      });
      recordAudit(db, { actor: req.authContext?.tokenId ?? 'unknown', action: 'group.update', resourceId: id });
      return reply.send({ group } satisfies GroupResponse);
    } catch (err) {
      return sendManagerError(reply, err);
    }
  });

  app.delete('/api/v1/groups/:id', async (req, reply) => {
    const { id } = req.params as { id: string };
    if (!gm.deleteGroup(id)) {
      return reply.status(404).send({ error: { code: 'NOT_FOUND', message: 'No group with this id' } });
    }
    recordAudit(db, { actor: req.authContext?.tokenId ?? 'unknown', action: 'group.delete', resourceId: id });
    return reply.send({ ok: true });
  });

  app.post('/api/v1/groups/:id/repos', async (req, reply) => {
    const { id } = req.params as { id: string };
    const body = req.body as Partial<AddRepoRequest> | null;
    if (!body || typeof body.repoId !== 'string') {
      return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'repoId is required' } });
    }
    try {
      return reply.send({ group: gm.addRepo(id, body.repoId) } satisfies GroupResponse);
    } catch (err) {
      return sendManagerError(reply, err);
    }
  });

  app.delete('/api/v1/groups/:id/repos/:repoId', async (req, reply) => {
    const { id, repoId } = req.params as { id: string; repoId: string };
    try {
      return reply.send({ group: gm.removeRepo(id, repoId) } satisfies GroupResponse);
    } catch (err) {
      return sendManagerError(reply, err);
    }
  });

  // Session de groupe (P6) : UNE session Claude couvrant tous les repos du groupe (--add-dir).
  // Les répertoires sont résolus côté serveur depuis les propres worktrees du groupe — le client
  // ne passe jamais de chemin brut. `branch` présent → worktree de cette branche par repo ;
  // absent → le worktree principal de chaque repo.
  app.post('/api/v1/groups/:id/session', async (req, reply) => {
    const { id } = req.params as { id: string };
    const body = (req.body as Partial<CreateGroupSessionRequest> | null) ?? {};
    const command = body.command ?? 'claude';
    if (command !== 'claude' && command !== 'bash') {
      return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'command must be claude or bash' } });
    }
    const branch = typeof body.branch === 'string' && body.branch.trim() !== '' ? body.branch.trim() : null;

    let group;
    try {
      group = gm.getGroup(id);
    } catch (err) {
      return sendManagerError(reply, err);
    }

    const dirs: string[] = [];
    const skipped: Array<{ repoId: string; reason: string }> = [];
    for (const repoId of group.repoIds) {
      let worktrees;
      try {
        worktrees = await wt.listRepoWorktrees(repoId);
      } catch (err) {
        skipped.push({ repoId, reason: err instanceof Error ? err.message : String(err) });
        continue;
      }
      const match = branch ? worktrees.find((w) => w.branch === branch) : worktrees.find((w) => w.isMain);
      if (!match) {
        skipped.push({ repoId, reason: branch ? `no worktree on branch ${branch}` : 'no main worktree' });
        continue;
      }
      if (!dirs.includes(match.path)) dirs.push(match.path);
    }

    if (dirs.length === 0) {
      return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'no directory to span (group has no resolvable worktree)' } });
    }
    // cwd = parent commun des repos couverts, chacun relié en --add-dir (P6). Voir resolveGroupCwd.
    const { cwd, addDirs } = resolveGroupCwd(dirs);

    try {
      const session = manager.spawn({ cwd, addDirs, command, groupId: id });
      recordAudit(db, {
        actor: req.authContext?.tokenId ?? 'unknown',
        action: 'group.session.create',
        resourceId: id,
        details: { command, dirs: dirs.length, ...(branch ? { branch } : {}) },
      });
      return reply.status(201).send({ session, dirs, skipped } satisfies GroupSessionResponse);
    } catch (err) {
      const statusCode = (err as { statusCode?: number }).statusCode ?? 500;
      return reply.status(statusCode).send({ error: { code: 'SPAWN_FAILED', message: (err as Error).message } });
    }
  });
}
