import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { Prisma } from '@prisma/client'
import { router, adminProcedure, protectedProcedure } from '../trpc'
import { logAudit } from '@/server/utils/audit'
import { validateRoundConfig, defaultRoundConfig } from '@/types/competition-configs'
import { generateShortlist } from '../services/ai-shortlist'
import {
  openWindow,
  closeWindow,
  lockWindow,
  checkDeadlinePolicy,
  validateSubmission,
  getVisibleWindows,
} from '../services/submission-manager'

const roundTypeEnum = z.enum([
  'INTAKE',
  'FILTERING',
  'EVALUATION',
  'SUBMISSION',
  'MENTORING',
  'LIVE_FINAL',
  'DELIBERATION',
])

export const roundRouter = router({
  /**
   * Create a new round within a competition
   */
  create: adminProcedure
    .input(
      z.object({
        competitionId: z.string(),
        name: z.string().min(1).max(255),
        slug: z.string().min(1).max(100).regex(/^[a-z0-9-]+$/),
        roundType: roundTypeEnum,
        sortOrder: z.number().int().nonnegative(),
        configJson: z.record(z.unknown()).optional(),
        windowOpenAt: z.date().nullable().optional(),
        windowCloseAt: z.date().nullable().optional(),
        juryGroupId: z.string().nullable().optional(),
        submissionWindowId: z.string().nullable().optional(),
        purposeKey: z.string().nullable().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify competition exists
      await ctx.prisma.competition.findUniqueOrThrow({
        where: { id: input.competitionId },
      })

      // Validate configJson against the Zod schema for this roundType
      const config = input.configJson
        ? validateRoundConfig(input.roundType, input.configJson)
        : defaultRoundConfig(input.roundType)

      const round = await ctx.prisma.round.create({
        data: {
          competitionId: input.competitionId,
          name: input.name,
          slug: input.slug,
          roundType: input.roundType,
          sortOrder: input.sortOrder,
          configJson: config as unknown as Prisma.InputJsonValue,
          windowOpenAt: input.windowOpenAt ?? undefined,
          windowCloseAt: input.windowCloseAt ?? undefined,
          juryGroupId: input.juryGroupId ?? undefined,
          submissionWindowId: input.submissionWindowId ?? undefined,
          purposeKey: input.purposeKey ?? undefined,
        },
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'Round',
        entityId: round.id,
        detailsJson: {
          name: input.name,
          roundType: input.roundType,
          competitionId: input.competitionId,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return round
    }),

  /**
   * Get round by ID with all relations
   */
  getById: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const round = await ctx.prisma.round.findUnique({
        where: { id: input.id },
        include: {
          juryGroup: {
            include: { members: true },
          },
          submissionWindow: {
            include: { fileRequirements: true },
          },
          advancementRules: { orderBy: { sortOrder: 'asc' } },
          visibleSubmissionWindows: {
            include: { submissionWindow: true },
          },
          _count: {
            select: { projectRoundStates: true },
          },
        },
      })

      if (!round) {
        throw new TRPCError({ code: 'NOT_FOUND', message: 'Round not found' })
      }

      return round
    }),

  /**
   * Update round settings/config
   */
  update: adminProcedure
    .input(
      z.object({
        id: z.string(),
        name: z.string().min(1).max(255).optional(),
        slug: z.string().min(1).max(100).regex(/^[a-z0-9-]+$/).optional(),
        status: z.enum(['ROUND_DRAFT', 'ROUND_ACTIVE', 'ROUND_CLOSED', 'ROUND_ARCHIVED']).optional(),
        configJson: z.record(z.unknown()).optional(),
        windowOpenAt: z.date().nullable().optional(),
        windowCloseAt: z.date().nullable().optional(),
        juryGroupId: z.string().nullable().optional(),
        submissionWindowId: z.string().nullable().optional(),
        purposeKey: z.string().nullable().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id, configJson, ...data } = input

      const existing = await ctx.prisma.round.findUniqueOrThrow({ where: { id } })

      // If configJson provided, validate it against the round type
      let validatedConfig: Prisma.InputJsonValue | undefined
      if (configJson) {
        const parsed = validateRoundConfig(existing.roundType, configJson)
        validatedConfig = parsed as unknown as Prisma.InputJsonValue
      }

      const round = await ctx.prisma.round.update({
        where: { id },
        data: {
          ...data,
          ...(validatedConfig !== undefined ? { configJson: validatedConfig } : {}),
        },
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'Round',
        entityId: id,
        detailsJson: {
          changes: input,
          previous: {
            name: existing.name,
            status: existing.status,
          },
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return round
    }),

  /**
   * Reorder rounds within a competition
   */
  updateOrder: adminProcedure
    .input(
      z.object({
        competitionId: z.string(),
        roundIds: z.array(z.string()),
      })
    )
    .mutation(async ({ ctx, input }) => {
      return ctx.prisma.$transaction(
        input.roundIds.map((roundId, index) =>
          ctx.prisma.round.update({
            where: { id: roundId },
            data: { sortOrder: index },
          })
        )
      )
    }),

  /**
   * Delete a round
   */
  delete: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const existing = await ctx.prisma.round.findUniqueOrThrow({ where: { id: input.id } })

      await ctx.prisma.round.delete({ where: { id: input.id } })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'DELETE',
        entityType: 'Round',
        entityId: input.id,
        detailsJson: {
          name: existing.name,
          roundType: existing.roundType,
          competitionId: existing.competitionId,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return existing
    }),

  // =========================================================================
  // Project Advancement (Manual Only)
  // =========================================================================

  /**
   * Advance PASSED projects from one round to the next.
   * This is ALWAYS manual — no auto-advancement after AI filtering.
   * Admin must explicitly trigger this after reviewing results.
   */
  advanceProjects: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        targetRoundId: z.string().optional(),
        projectIds: z.array(z.string()).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { roundId, targetRoundId, projectIds } = input

      // Get current round with competition context
      const currentRound = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: roundId },
        select: { id: true, name: true, competitionId: true, sortOrder: true },
      })

      // Determine target round
      let targetRound: { id: string; name: string }
      if (targetRoundId) {
        targetRound = await ctx.prisma.round.findUniqueOrThrow({
          where: { id: targetRoundId },
          select: { id: true, name: true },
        })
      } else {
        // Find next round in same competition by sortOrder
        const nextRound = await ctx.prisma.round.findFirst({
          where: {
            competitionId: currentRound.competitionId,
            sortOrder: { gt: currentRound.sortOrder },
          },
          orderBy: { sortOrder: 'asc' },
          select: { id: true, name: true },
        })
        if (!nextRound) {
          throw new TRPCError({
            code: 'BAD_REQUEST',
            message: 'No subsequent round exists in this competition. Create the next round first.',
          })
        }
        targetRound = nextRound
      }

      // Determine which projects to advance
      let idsToAdvance: string[]
      if (projectIds && projectIds.length > 0) {
        idsToAdvance = projectIds
      } else {
        // Default: all PASSED projects in current round
        const passedStates = await ctx.prisma.projectRoundState.findMany({
          where: { roundId, state: 'PASSED' },
          select: { projectId: true },
        })
        idsToAdvance = passedStates.map((s) => s.projectId)
      }

      if (idsToAdvance.length === 0) {
        return { advancedCount: 0, targetRoundId: targetRound.id, targetRoundName: targetRound.name }
      }

      // Transaction: create entries in target round + mark current as COMPLETED
      await ctx.prisma.$transaction(async (tx) => {
        // Create ProjectRoundState in target round
        await tx.projectRoundState.createMany({
          data: idsToAdvance.map((projectId) => ({
            projectId,
            roundId: targetRound.id,
          })),
          skipDuplicates: true,
        })

        // Mark current round states as COMPLETED
        await tx.projectRoundState.updateMany({
          where: {
            roundId,
            projectId: { in: idsToAdvance },
            state: 'PASSED',
          },
          data: { state: 'COMPLETED' },
        })

        // Update project status to ASSIGNED
        await tx.project.updateMany({
          where: { id: { in: idsToAdvance } },
          data: { status: 'ASSIGNED' },
        })

        // Status history
        await tx.projectStatusHistory.createMany({
          data: idsToAdvance.map((projectId) => ({
            projectId,
            status: 'ASSIGNED',
            changedBy: ctx.user?.id,
          })),
        })
      })

      // Audit
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'ADVANCE_PROJECTS',
        entityType: 'Round',
        entityId: roundId,
        detailsJson: {
          fromRound: currentRound.name,
          toRound: targetRound.name,
          targetRoundId: targetRound.id,
          projectCount: idsToAdvance.length,
          projectIds: idsToAdvance,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return {
        advancedCount: idsToAdvance.length,
        targetRoundId: targetRound.id,
        targetRoundName: targetRound.name,
      }
    }),

  // =========================================================================
  // AI Shortlist Recommendations
  // =========================================================================

  /**
   * Generate AI-powered shortlist recommendations for a round.
   * Runs independently for STARTUP and BUSINESS_CONCEPT categories.
   * Uses per-round config for advancement targets and file parsing.
   */
  generateAIRecommendations: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        rubric: z.string().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: {
          id: true,
          name: true,
          competitionId: true,
          configJson: true,
        },
      })

      const config = (round.configJson as Record<string, unknown>) ?? {}
      const startupTopN = (config.startupAdvanceCount as number) || 10
      const conceptTopN = (config.conceptAdvanceCount as number) || 10
      const aiParseFiles = !!config.aiParseFiles

      const result = await generateShortlist(
        {
          roundId: input.roundId,
          competitionId: round.competitionId,
          startupTopN,
          conceptTopN,
          rubric: input.rubric,
          aiParseFiles,
        },
        ctx.prisma,
      )

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'AI_SHORTLIST',
        entityType: 'Round',
        entityId: input.roundId,
        detailsJson: {
          roundName: round.name,
          startupTopN,
          conceptTopN,
          aiParseFiles,
          success: result.success,
          startupCount: result.recommendations.STARTUP.length,
          conceptCount: result.recommendations.BUSINESS_CONCEPT.length,
          tokensUsed: result.tokensUsed,
          errors: result.errors,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return result
    }),

  // =========================================================================
  // Submission Window Management
  // =========================================================================

  /**
   * Create a submission window for a round
   */
  createSubmissionWindow: adminProcedure
    .input(
      z.object({
        competitionId: z.string(),
        name: z.string().min(1).max(255),
        slug: z.string().min(1).max(100).regex(/^[a-z0-9-]+$/),
        roundNumber: z.number().int().min(1),
        windowOpenAt: z.date().optional(),
        windowCloseAt: z.date().optional(),
        deadlinePolicy: z.enum(['HARD_DEADLINE', 'FLAG', 'GRACE']).default('HARD_DEADLINE'),
        graceHours: z.number().int().min(0).optional(),
        lockOnClose: z.boolean().default(true),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const window = await ctx.prisma.submissionWindow.create({
        data: {
          competitionId: input.competitionId,
          name: input.name,
          slug: input.slug,
          roundNumber: input.roundNumber,
          windowOpenAt: input.windowOpenAt,
          windowCloseAt: input.windowCloseAt,
          deadlinePolicy: input.deadlinePolicy,
          graceHours: input.graceHours,
          lockOnClose: input.lockOnClose,
        },
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'SubmissionWindow',
        entityId: window.id,
        detailsJson: { name: input.name, competitionId: input.competitionId },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return window
    }),

  /**
   * Update an existing submission window
   */
  updateSubmissionWindow: adminProcedure
    .input(
      z.object({
        id: z.string(),
        name: z.string().min(1).max(255).optional(),
        slug: z.string().min(1).max(100).regex(/^[a-z0-9-]+$/).optional(),
        roundNumber: z.number().int().min(1).optional(),
        windowOpenAt: z.date().nullable().optional(),
        windowCloseAt: z.date().nullable().optional(),
        deadlinePolicy: z.enum(['HARD_DEADLINE', 'FLAG', 'GRACE']).optional(),
        graceHours: z.number().int().min(0).nullable().optional(),
        lockOnClose: z.boolean().optional(),
        sortOrder: z.number().int().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id, ...data } = input
      const window = await ctx.prisma.submissionWindow.update({
        where: { id },
        data,
      })
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'SubmissionWindow',
        entityId: id,
        detailsJson: data,
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })
      return window
    }),

  /**
   * Delete a submission window (only if no files uploaded)
   */
  deleteSubmissionWindow: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      // Check if window has uploaded files
      const window = await ctx.prisma.submissionWindow.findUniqueOrThrow({
        where: { id: input.id },
        select: { id: true, name: true, _count: { select: { projectFiles: true } } },
      })
      if (window._count.projectFiles > 0) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: `Cannot delete window "${window.name}" — it has ${window._count.projectFiles} uploaded files. Remove files first.`,
        })
      }
      await ctx.prisma.submissionWindow.delete({ where: { id: input.id } })
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'DELETE',
        entityType: 'SubmissionWindow',
        entityId: input.id,
        detailsJson: { name: window.name },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })
      return { success: true }
    }),

  /**
   * Open a submission window
   */
  openSubmissionWindow: adminProcedure
    .input(z.object({ windowId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const result = await openWindow(input.windowId, ctx.user.id, ctx.prisma)
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to open window',
        })
      }
      return result
    }),

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

  /**
   * Lock a submission window
   */
  lockSubmissionWindow: adminProcedure
    .input(z.object({ windowId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const result = await lockWindow(input.windowId, ctx.user.id, ctx.prisma)
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to lock window',
        })
      }
      return result
    }),

  /**
   * Check deadline status of a window
   */
  checkDeadline: protectedProcedure
    .input(z.object({ windowId: z.string() }))
    .query(async ({ ctx, input }) => {
      return checkDeadlinePolicy(input.windowId, ctx.prisma)
    }),

  /**
   * Validate files against window requirements
   */
  validateSubmission: protectedProcedure
    .input(
      z.object({
        projectId: z.string(),
        windowId: z.string(),
        files: z.array(
          z.object({
            mimeType: z.string(),
            size: z.number(),
            requirementId: z.string().optional(),
          })
        ),
      })
    )
    .mutation(async ({ ctx, input }) => {
      return validateSubmission(input.projectId, input.windowId, input.files, ctx.prisma)
    }),

  /**
   * Get visible submission windows for a round
   */
  getVisibleWindows: protectedProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      return getVisibleWindows(input.roundId, ctx.prisma)
    }),

  // =========================================================================
  // File Requirements Management
  // =========================================================================

  /**
   * Create a file requirement for a submission window
   */
  createFileRequirement: adminProcedure
    .input(
      z.object({
        submissionWindowId: z.string(),
        slug: z.string().min(1).max(100).regex(/^[a-z0-9-]+$/),
        label: z.string().min(1).max(255),
        description: z.string().max(2000).optional(),
        mimeTypes: z.array(z.string()).default([]),
        maxSizeMb: z.number().int().min(0).optional(),
        required: z.boolean().default(false),
        sortOrder: z.number().int().default(0),
      })
    )
    .mutation(async ({ ctx, input }) => {
      return ctx.prisma.submissionFileRequirement.create({
        data: input,
      })
    }),

  /**
   * Update a file requirement
   */
  updateFileRequirement: adminProcedure
    .input(
      z.object({
        id: z.string(),
        label: z.string().min(1).max(255).optional(),
        description: z.string().max(2000).optional().nullable(),
        mimeTypes: z.array(z.string()).optional(),
        maxSizeMb: z.number().min(0).optional().nullable(),
        required: z.boolean().optional(),
        sortOrder: z.number().int().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id, ...data } = input
      return ctx.prisma.submissionFileRequirement.update({
        where: { id },
        data,
      })
    }),

  /**
   * Delete a file requirement
   */
  deleteFileRequirement: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      return ctx.prisma.submissionFileRequirement.delete({
        where: { id: input.id },
      })
    }),

  /**
   * Get submission windows for applicants in a competition
   */
  getApplicantWindows: protectedProcedure
    .input(z.object({ competitionId: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.submissionWindow.findMany({
        where: { competitionId: input.competitionId },
        include: {
          fileRequirements: { orderBy: { sortOrder: 'asc' } },
        },
        orderBy: { sortOrder: 'asc' },
      })
    }),
})
