/**
 * Project Router — register, list, get, delete, initiative associations
 */

import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { join } from 'node:path';
import { rm, access } from 'node:fs/promises';
import type { ProcedureBuilder } from '../trpc.js';
import { requireProjectRepository, requireProjectSyncManager } from './_helpers.js';
import { cloneProject } from '../../git/clone.js';
import { getProjectCloneDir } from '../../git/project-clones.js';

export function projectProcedures(publicProcedure: ProcedureBuilder) {
  return {
    registerProject: publicProcedure
      .input(z.object({
        name: z.string().min(1),
        url: z.string().min(1),
        defaultBranch: z.string().min(1).optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const repo = requireProjectRepository(ctx);

        let project;
        try {
          project = await repo.create({
            name: input.name,
            url: input.url,
            ...(input.defaultBranch && { defaultBranch: input.defaultBranch }),
          });
        } catch (error) {
          const msg = (error as Error).message;
          if (msg.includes('UNIQUE') || msg.includes('unique')) {
            throw new TRPCError({
              code: 'CONFLICT',
              message: `A project with that name or URL already exists`,
            });
          }
          throw error;
        }

        if (ctx.workspaceRoot) {
          const clonePath = join(ctx.workspaceRoot, getProjectCloneDir(input.name, project.id));

          try {
            await cloneProject(input.url, clonePath);
          } catch (cloneError) {
            await repo.delete(project.id);
            throw new TRPCError({
              code: 'INTERNAL_SERVER_ERROR',
              message: `Failed to clone repository: ${(cloneError as Error).message}`,
            });
          }

          // Validate that the specified default branch exists in the cloned repo
          const branchToValidate = input.defaultBranch ?? 'main';
          if (ctx.branchManager) {
            const exists = await ctx.branchManager.remoteBranchExists(clonePath, branchToValidate);
            if (!exists) {
              // Clean up: remove project and clone
              await rm(clonePath, { recursive: true, force: true }).catch(() => {});
              await repo.delete(project.id);
              throw new TRPCError({
                code: 'BAD_REQUEST',
                message: `Branch '${branchToValidate}' does not exist in the repository`,
              });
            }
          }
        }

        // Check for preview config
        let hasPreviewConfig = false;
        if (ctx.workspaceRoot) {
          const clonePath = join(ctx.workspaceRoot, getProjectCloneDir(input.name, project.id));
          try {
            await access(join(clonePath, '.cw-preview.yml'));
            hasPreviewConfig = true;
          } catch { /* no config */ }
        }

        return { ...project, hasPreviewConfig };
      }),

    listProjects: publicProcedure
      .query(async ({ ctx }) => {
        const repo = requireProjectRepository(ctx);
        return repo.findAll();
      }),

    getProject: publicProcedure
      .input(z.object({ id: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const repo = requireProjectRepository(ctx);
        const project = await repo.findById(input.id);
        if (!project) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: `Project '${input.id}' not found`,
          });
        }
        return project;
      }),

    deleteProject: publicProcedure
      .input(z.object({ id: z.string().min(1) }))
      .mutation(async ({ ctx, input }) => {
        const repo = requireProjectRepository(ctx);
        const project = await repo.findById(input.id);
        if (project && ctx.workspaceRoot) {
          const clonePath = join(ctx.workspaceRoot, getProjectCloneDir(project.name, project.id));
          await rm(clonePath, { recursive: true, force: true }).catch(() => {});
        }
        await repo.delete(input.id);
        return { success: true };
      }),

    updateProject: publicProcedure
      .input(z.object({
        id: z.string().min(1),
        defaultBranch: z.string().min(1).optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const repo = requireProjectRepository(ctx);
        const { id, ...data } = input;
        const existing = await repo.findById(id);
        if (!existing) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: `Project '${id}' not found`,
          });
        }

        // Validate that the new default branch exists in the repo
        if (data.defaultBranch && ctx.workspaceRoot && ctx.branchManager) {
          const clonePath = join(ctx.workspaceRoot, getProjectCloneDir(existing.name, existing.id));
          const exists = await ctx.branchManager.remoteBranchExists(clonePath, data.defaultBranch);
          if (!exists) {
            throw new TRPCError({
              code: 'BAD_REQUEST',
              message: `Branch '${data.defaultBranch}' does not exist in the repository`,
            });
          }
        }

        return repo.update(id, data);
      }),

    getInitiativeProjects: publicProcedure
      .input(z.object({ initiativeId: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const repo = requireProjectRepository(ctx);
        return repo.findProjectsByInitiativeId(input.initiativeId);
      }),

    updateInitiativeProjects: publicProcedure
      .input(z.object({
        initiativeId: z.string().min(1),
        projectIds: z.array(z.string().min(1)).min(1),
      }))
      .mutation(async ({ ctx, input }) => {
        const repo = requireProjectRepository(ctx);
        await repo.setInitiativeProjects(input.initiativeId, input.projectIds);
        return { success: true };
      }),

    syncProject: publicProcedure
      .input(z.object({ id: z.string().min(1) }))
      .mutation(async ({ ctx, input }) => {
        const syncManager = requireProjectSyncManager(ctx);
        return syncManager.syncProject(input.id);
      }),

    syncAllProjects: publicProcedure
      .mutation(async ({ ctx }) => {
        const syncManager = requireProjectSyncManager(ctx);
        return syncManager.syncAllProjects();
      }),

    getProjectSyncStatus: publicProcedure
      .input(z.object({ id: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const syncManager = requireProjectSyncManager(ctx);
        return syncManager.getSyncStatus(input.id);
      }),
  };
}
