import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, adminProcedure, protectedProcedure } from '../trpc'
import {
  activateRound,
  closeRound,
  archiveRound,
  reopenRound,
  transitionProject,
  batchTransitionProjects,
  getProjectRoundStates,
  getProjectRoundState,
} from '../services/round-engine'

const projectRoundStateEnum = z.enum([
  'PENDING',
  'IN_PROGRESS',
  'PASSED',
  'REJECTED',
  'COMPLETED',
  'WITHDRAWN',
])

export const roundEngineRouter = router({
  /**
   * Activate a round: ROUND_DRAFT → ROUND_ACTIVE
   */
  activate: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const result = await activateRound(input.roundId, ctx.user.id, ctx.prisma)
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to activate round',
        })
      }
      return result
    }),

  /**
   * Close a round: ROUND_ACTIVE → ROUND_CLOSED
   */
  close: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const result = await closeRound(input.roundId, ctx.user.id, ctx.prisma)
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to close round',
        })
      }
      return result
    }),

  /**
   * Reopen a round: ROUND_CLOSED → ROUND_ACTIVE
   * Pauses any subsequent active rounds in the same competition.
   */
  reopen: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const result = await reopenRound(input.roundId, ctx.user.id, ctx.prisma)
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to reopen round',
        })
      }
      return result
    }),

  /**
   * Archive a round: ROUND_CLOSED → ROUND_ARCHIVED
   */
  archive: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const result = await archiveRound(input.roundId, ctx.user.id, ctx.prisma)
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to archive round',
        })
      }
      return result
    }),

  /**
   * Transition a single project within a round
   */
  transitionProject: adminProcedure
    .input(
      z.object({
        projectId: z.string(),
        roundId: z.string(),
        newState: projectRoundStateEnum,
      })
    )
    .mutation(async ({ ctx, input }) => {
      const result = await transitionProject(
        input.projectId,
        input.roundId,
        input.newState,
        ctx.user.id,
        ctx.prisma,
      )
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to transition project',
        })
      }
      return result
    }),

  /**
   * Batch transition multiple projects within a round
   */
  batchTransition: adminProcedure
    .input(
      z.object({
        projectIds: z.array(z.string()).min(1),
        roundId: z.string(),
        newState: projectRoundStateEnum,
      })
    )
    .mutation(async ({ ctx, input }) => {
      return batchTransitionProjects(
        input.projectIds,
        input.roundId,
        input.newState,
        ctx.user.id,
        ctx.prisma,
      )
    }),

  /**
   * Get all project round states for a round
   */
  getProjectStates: protectedProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      return getProjectRoundStates(input.roundId, ctx.prisma)
    }),

  /**
   * Get a single project's state within a round
   */
  getProjectState: protectedProcedure
    .input(
      z.object({
        projectId: z.string(),
        roundId: z.string(),
      })
    )
    .query(async ({ ctx, input }) => {
      return getProjectRoundState(input.projectId, input.roundId, ctx.prisma)
    }),

  /**
   * Remove a project from a round (and all subsequent rounds in that competition).
   * The project remains in all prior rounds.
   */
  removeFromRound: adminProcedure
    .input(
      z.object({
        projectId: z.string(),
        roundId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Get the round to know its competition and sort order
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { id: true, competitionId: true, sortOrder: true },
      })

      // Find all rounds at this sort order or later in the same competition
      const roundsToRemoveFrom = await ctx.prisma.round.findMany({
        where: {
          competitionId: round.competitionId,
          sortOrder: { gte: round.sortOrder },
        },
        select: { id: true },
      })

      const roundIds = roundsToRemoveFrom.map((r) => r.id)

      // Delete ProjectRoundState entries for this project in all affected rounds
      const deleted = await ctx.prisma.projectRoundState.deleteMany({
        where: {
          projectId: input.projectId,
          roundId: { in: roundIds },
        },
      })

      // Check if the project is still in any round at all
      const remainingStates = await ctx.prisma.projectRoundState.count({
        where: { projectId: input.projectId },
      })

      // If no longer in any round, reset project status back to SUBMITTED
      if (remainingStates === 0) {
        await ctx.prisma.project.update({
          where: { id: input.projectId },
          data: { status: 'SUBMITTED' },
        })
      }

      return { success: true, removedFromRounds: deleted.count }
    }),

  /**
   * Batch remove projects from a round (and all subsequent rounds).
   */
  batchRemoveFromRound: adminProcedure
    .input(
      z.object({
        projectIds: z.array(z.string()).min(1),
        roundId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { id: true, competitionId: true, sortOrder: true },
      })

      const roundsToRemoveFrom = await ctx.prisma.round.findMany({
        where: {
          competitionId: round.competitionId,
          sortOrder: { gte: round.sortOrder },
        },
        select: { id: true },
      })

      const roundIds = roundsToRemoveFrom.map((r) => r.id)

      const deleted = await ctx.prisma.projectRoundState.deleteMany({
        where: {
          projectId: { in: input.projectIds },
          roundId: { in: roundIds },
        },
      })

      // For projects with no remaining round states, reset to SUBMITTED
      const projectsStillInRounds = await ctx.prisma.projectRoundState.findMany({
        where: { projectId: { in: input.projectIds } },
        select: { projectId: true },
        distinct: ['projectId'],
      })
      const stillInRoundIds = new Set(projectsStillInRounds.map((p) => p.projectId))
      const orphanedIds = input.projectIds.filter((id) => !stillInRoundIds.has(id))

      if (orphanedIds.length > 0) {
        await ctx.prisma.project.updateMany({
          where: { id: { in: orphanedIds } },
          data: { status: 'SUBMITTED' },
        })
      }

      return { success: true, removedCount: deleted.count }
    }),
})
