import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, adminProcedure } from '../trpc'
import {
  activateRound,
  closeRound,
  archiveRound,
  reopenRound,
  transitionProject,
  batchTransitionProjects,
  getProjectRoundStates,
  getProjectRoundState,
} from '../services/round-engine'
import {
  processRoundClose,
  getFinalizationSummary,
  confirmFinalization,
  findDisplayNextRound,
} from '../services/round-finalization'
import {
  getAdvancementNotificationTemplate,
  getAwardWinnerNotificationTemplate,
  getRejectionNotificationTemplate,
} from '@/lib/email'

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,
        { adminOverride: true },
      )
      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,
        { adminOverride: true },
      )
    }),

  /**
   * Get all project round states for a round
   */
  getProjectStates: adminProcedure
    .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: adminProcedure
    .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 Assignment records first (Prisma cascade handles Evaluations)
      await ctx.prisma.assignment.deleteMany({
        where: {
          projectId: input.projectId,
          roundId: { in: roundIds },
        },
      })

      // 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)

      // Delete Assignment records first (Prisma cascade handles Evaluations)
      await ctx.prisma.assignment.deleteMany({
        where: {
          projectId: { in: input.projectIds },
          roundId: { in: roundIds },
        },
      })

      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 }
    }),

  /**
   * Retroactive document check: auto-PASS any PENDING/IN_PROGRESS projects
   * that already have all required documents uploaded for this round.
   * Useful for rounds activated before the auto-transition feature was deployed.
   */
  checkDocumentCompletion: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const { batchCheckRequirementsAndTransition } = await import('../services/round-engine')

      const projectStates = await ctx.prisma.projectRoundState.findMany({
        where: {
          roundId: input.roundId,
          state: { in: ['PENDING', 'IN_PROGRESS'] },
        },
        select: { projectId: true },
      })

      if (projectStates.length === 0) {
        return { transitionedCount: 0, checkedCount: 0, projectIds: [] }
      }

      const projectIds = projectStates.map((ps: { projectId: string }) => ps.projectId)
      const result = await batchCheckRequirementsAndTransition(
        input.roundId,
        projectIds,
        ctx.user.id,
        ctx.prisma,
      )

      return {
        transitionedCount: result.transitionedCount,
        checkedCount: projectIds.length,
        projectIds: result.projectIds,
      }
    }),

  // ─── Finalization Procedures ────────────────────────────────────────────

  getFinalizationSummary: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      return getFinalizationSummary(input.roundId, ctx.prisma)
    }),

  updateProposedOutcome: adminProcedure
    .input(z.object({
      roundId: z.string(),
      projectId: z.string(),
      proposedOutcome: projectRoundStateEnum,
    }))
    .mutation(async ({ ctx, input }) => {
      const prs = await ctx.prisma.projectRoundState.findUnique({
        where: { projectId_roundId: { projectId: input.projectId, roundId: input.roundId } },
      })
      if (!prs) throw new TRPCError({ code: 'NOT_FOUND', message: 'Project round state not found' })

      return ctx.prisma.projectRoundState.update({
        where: { id: prs.id },
        data: { proposedOutcome: input.proposedOutcome },
      })
    }),

  batchUpdateProposedOutcomes: adminProcedure
    .input(z.object({
      roundId: z.string(),
      outcomes: z.record(z.string(), projectRoundStateEnum),
    }))
    .mutation(async ({ ctx, input }) => {
      let updated = 0
      for (const [projectId, outcome] of Object.entries(input.outcomes)) {
        await ctx.prisma.projectRoundState.updateMany({
          where: { projectId, roundId: input.roundId },
          data: { proposedOutcome: outcome },
        })
        updated++
      }
      return { updated }
    }),

  previewFinalizationAdvancementEmail: adminProcedure
    .input(z.object({
      roundId: z.string(),
      customMessage: z.string().optional(),
    }))
    .query(async ({ ctx, input }) => {
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: {
          name: true,
          competition: {
            select: {
              name: true,
              rounds: {
                select: { id: true, name: true, sortOrder: true, roundType: true },
                orderBy: { sortOrder: 'asc' },
              },
            },
          },
          specialAward: { select: { name: true } },
        },
      })
      const rounds = round.competition.rounds
      // Skip MENTORING rounds for display — those are opt-in and not the
      // shared destination for all advancing teams.
      const displayNext = findDisplayNextRound(rounds, input.roundId)

      const passedCount = await ctx.prisma.projectRoundState.count({
        where: { roundId: input.roundId, proposedOutcome: 'PASSED' },
      })

      // No further round → passing teams are winners (of the special award if
      // attached, otherwise of the competition itself).
      const winnerLabel = !displayNext
        ? round.specialAward?.name ?? round.competition.name
        : null

      const template = winnerLabel
        ? getAwardWinnerNotificationTemplate(
            'Team Member',
            'Your Project',
            winnerLabel,
            input.customMessage || undefined,
          )
        : getAdvancementNotificationTemplate(
            'Team Member',
            'Your Project',
            round.name,
            displayNext?.name ?? 'Next Round',
            input.customMessage || undefined,
          )

      return { html: template.html, subject: template.subject, recipientCount: passedCount }
    }),

  previewFinalizationRejectionEmail: adminProcedure
    .input(z.object({
      roundId: z.string(),
      customMessage: z.string().optional(),
    }))
    .query(async ({ ctx, input }) => {
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { name: true },
      })

      const rejectedCount = await ctx.prisma.projectRoundState.count({
        where: { roundId: input.roundId, proposedOutcome: 'REJECTED' },
      })

      const template = getRejectionNotificationTemplate(
        'Team Member',
        'Your Project',
        round.name,
        input.customMessage || undefined,
      )

      return { html: template.html, subject: template.subject, recipientCount: rejectedCount }
    }),

  confirmFinalization: adminProcedure
    .input(z.object({
      roundId: z.string(),
      targetRoundId: z.string().optional(),
      advancementMessage: z.string().optional(),
      rejectionMessage: z.string().optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      return confirmFinalization(
        input.roundId,
        {
          targetRoundId: input.targetRoundId,
          advancementMessage: input.advancementMessage,
          rejectionMessage: input.rejectionMessage,
        },
        ctx.user.id,
        ctx.prisma,
      )
    }),

  endGracePeriod: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const round = await ctx.prisma.round.findUniqueOrThrow({ where: { id: input.roundId } })

      if (round.status !== 'ROUND_CLOSED') {
        throw new TRPCError({ code: 'BAD_REQUEST', message: 'Round must be ROUND_CLOSED' })
      }
      if (!round.gracePeriodEndsAt) {
        throw new TRPCError({ code: 'BAD_REQUEST', message: 'Round has no grace period set' })
      }

      // Clear grace period and process
      await ctx.prisma.round.update({
        where: { id: input.roundId },
        data: { gracePeriodEndsAt: new Date() },
      })

      const result = await processRoundClose(input.roundId, ctx.user.id, ctx.prisma)
      return { processed: result.processed }
    }),

  /**
   * Manually trigger processRoundClose for already-closed rounds.
   * Used when a round was closed before the finalization system existed,
   * or when processRoundClose failed silently on close.
   */
  processRoundProjects: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const round = await ctx.prisma.round.findUniqueOrThrow({ where: { id: input.roundId } })

      if (round.status !== 'ROUND_CLOSED' && round.status !== 'ROUND_ARCHIVED') {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: `Round must be ROUND_CLOSED or ROUND_ARCHIVED, got ${round.status}`,
        })
      }

      const result = await processRoundClose(input.roundId, ctx.user.id, ctx.prisma)
      return { processed: result.processed }
    }),
})
