import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, protectedProcedure, adminProcedure } from '../trpc'
import { getUserAvatarUrl } from '../utils/avatar-url'
import {
  generateAIAssignments,
  generateFallbackAssignments,
  type AssignmentProgressCallback,
} from '../services/ai-assignment'
import { isOpenAIConfigured } from '@/lib/openai'
import { prisma } from '@/lib/prisma'
import {
  createNotification,
  createBulkNotifications,
  notifyAdmins,
  NotificationTypes,
} from '../services/in-app-notification'
import { logAudit } from '@/server/utils/audit'

async function runAIAssignmentJob(jobId: string, roundId: string, userId: string) {
  try {
    await prisma.assignmentJob.update({
      where: { id: jobId },
      data: { status: 'RUNNING', startedAt: new Date() },
    })

    const round = await prisma.round.findUniqueOrThrow({
      where: { id: roundId },
      select: {
        name: true,
        configJson: true,
        competitionId: true,
      },
    })

    const config = (round.configJson ?? {}) as Record<string, unknown>
    const requiredReviews = (config.requiredReviews as number) ?? 3
    const minAssignmentsPerJuror =
      (config.minLoadPerJuror as number) ??
      (config.minAssignmentsPerJuror as number) ??
      1
    const maxAssignmentsPerJuror =
      (config.maxLoadPerJuror as number) ??
      (config.maxAssignmentsPerJuror as number) ??
      20

    const jurors = await prisma.user.findMany({
      where: { role: 'JURY_MEMBER', status: 'ACTIVE' },
      select: {
        id: true,
        name: true,
        email: true,
        expertiseTags: true,
        maxAssignments: true,
        _count: {
          select: {
            assignments: { where: { roundId } },
          },
        },
      },
    })

    const projectRoundStates = await prisma.projectRoundState.findMany({
      where: { roundId },
      select: { projectId: true },
    })
    const projectIds = projectRoundStates.map((prs) => prs.projectId)

    const projects = await prisma.project.findMany({
      where: { id: { in: projectIds } },
      select: {
        id: true,
        title: true,
        description: true,
        tags: true,
        teamName: true,
        _count: { select: { assignments: { where: { roundId } } } },
      },
    })

    const existingAssignments = await prisma.assignment.findMany({
      where: { roundId },
      select: { userId: true, projectId: true },
    })

    // Calculate batch info
    const BATCH_SIZE = 15
    const totalBatches = Math.ceil(projects.length / BATCH_SIZE)

    await prisma.assignmentJob.update({
      where: { id: jobId },
      data: { totalProjects: projects.length, totalBatches },
    })

    // Progress callback
    const onProgress: AssignmentProgressCallback = async (progress) => {
      await prisma.assignmentJob.update({
        where: { id: jobId },
        data: {
          currentBatch: progress.currentBatch,
          processedCount: progress.processedCount,
        },
      })
    }

    // Build per-juror limits map for jurors with personal maxAssignments
    const jurorLimits: Record<string, number> = {}
    for (const juror of jurors) {
      if (juror.maxAssignments !== null && juror.maxAssignments !== undefined) {
        jurorLimits[juror.id] = juror.maxAssignments
      }
    }

    const constraints = {
      requiredReviewsPerProject: requiredReviews,
      minAssignmentsPerJuror,
      maxAssignmentsPerJuror,
      jurorLimits: Object.keys(jurorLimits).length > 0 ? jurorLimits : undefined,
      existingAssignments: existingAssignments.map((a) => ({
        jurorId: a.userId,
        projectId: a.projectId,
      })),
    }

    const result = await generateAIAssignments(
      jurors,
      projects,
      constraints,
      userId,
      roundId,
      onProgress
    )

    // Enrich suggestions with names for storage
    const enrichedSuggestions = result.suggestions.map((s) => {
      const juror = jurors.find((j) => j.id === s.jurorId)
      const project = projects.find((p) => p.id === s.projectId)
      return {
        ...s,
        jurorName: juror?.name || juror?.email || 'Unknown',
        projectTitle: project?.title || 'Unknown',
      }
    })

    // Mark job as completed and store suggestions
    await prisma.assignmentJob.update({
      where: { id: jobId },
      data: {
        status: 'COMPLETED',
        completedAt: new Date(),
        processedCount: projects.length,
        suggestionsCount: result.suggestions.length,
        suggestionsJson: enrichedSuggestions,
        fallbackUsed: result.fallbackUsed ?? false,
      },
    })

    await notifyAdmins({
      type: NotificationTypes.AI_SUGGESTIONS_READY,
      title: 'AI Assignment Suggestions Ready',
      message: `AI generated ${result.suggestions.length} assignment suggestions for ${round.name || 'round'}${result.fallbackUsed ? ' (using fallback algorithm)' : ''}.`,
      linkUrl: `/admin/rounds/${roundId}`,
      linkLabel: 'View Suggestions',
      priority: 'high',
      metadata: {
        roundId,
        jobId,
        projectCount: projects.length,
        suggestionsCount: result.suggestions.length,
        fallbackUsed: result.fallbackUsed,
      },
    })

  } catch (error) {
    console.error('[AI Assignment Job] Error:', error)

    // Mark job as failed
    await prisma.assignmentJob.update({
      where: { id: jobId },
      data: {
        status: 'FAILED',
        errorMessage: error instanceof Error ? error.message : 'Unknown error',
        completedAt: new Date(),
      },
    })
  }
}

export const assignmentRouter = router({
  listByStage: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.assignment.findMany({
        where: { roundId: input.roundId },
        include: {
          user: { select: { id: true, name: true, email: true, expertiseTags: true } },
          project: { select: { id: true, title: true, tags: true } },
          evaluation: { select: { status: true, submittedAt: true } },
        },
        orderBy: { createdAt: 'desc' },
      })
    }),

  /**
   * List assignments for a project (admin only)
   */
  listByProject: adminProcedure
    .input(z.object({ projectId: z.string() }))
    .query(async ({ ctx, input }) => {
      const assignments = await ctx.prisma.assignment.findMany({
        where: { projectId: input.projectId },
        include: {
          user: { select: { id: true, name: true, email: true, expertiseTags: true, profileImageKey: true, profileImageProvider: true } },
          evaluation: { select: { status: true, submittedAt: true, globalScore: true, binaryDecision: true } },
        },
        orderBy: { createdAt: 'desc' },
      })

      // Attach avatar URLs
      return Promise.all(
        assignments.map(async (a) => ({
          ...a,
          user: {
            ...a.user,
            avatarUrl: await getUserAvatarUrl(a.user.profileImageKey, a.user.profileImageProvider),
          },
        }))
      )
    }),

  /**
   * Get my assignments (for jury members)
   */
  myAssignments: protectedProcedure
    .input(
      z.object({
        roundId: z.string().optional(),
        status: z.enum(['all', 'pending', 'completed']).default('all'),
      })
    )
    .query(async ({ ctx, input }) => {
      const where: Record<string, unknown> = {
        userId: ctx.user.id,
        round: { status: 'ROUND_ACTIVE' },
      }

      if (input.roundId) {
        where.roundId = input.roundId
      }

      if (input.status === 'pending') {
        where.isCompleted = false
      } else if (input.status === 'completed') {
        where.isCompleted = true
      }

      return ctx.prisma.assignment.findMany({
        where,
        include: {
          project: {
            include: { files: true },
          },
          round: true,
          evaluation: true,
        },
        orderBy: [{ isCompleted: 'asc' }, { createdAt: 'asc' }],
      })
    }),

  /**
   * Get assignment by ID
   */
  get: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const assignment = await ctx.prisma.assignment.findUniqueOrThrow({
        where: { id: input.id },
        include: {
          user: { select: { id: true, name: true, email: true } },
          project: { include: { files: true } },
          round: { include: { evaluationForms: { where: { isActive: true } } } },
          evaluation: true,
        },
      })

      // Verify access
      if (
        ctx.user.role === 'JURY_MEMBER' &&
        assignment.userId !== ctx.user.id
      ) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'You do not have access to this assignment',
        })
      }

      return assignment
    }),

  /**
   * Create a single assignment (admin only)
   */
  create: adminProcedure
    .input(
      z.object({
        userId: z.string(),
        projectId: z.string(),
        roundId: z.string(),
        isRequired: z.boolean().default(true),
        forceOverride: z.boolean().default(false),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const existing = await ctx.prisma.assignment.findUnique({
        where: {
          userId_projectId_roundId: {
            userId: input.userId,
            projectId: input.projectId,
            roundId: input.roundId,
          },
        },
      })

      if (existing) {
        throw new TRPCError({
          code: 'CONFLICT',
          message: 'This assignment already exists',
        })
      }

      const [stage, user] = await Promise.all([
        ctx.prisma.round.findUniqueOrThrow({
          where: { id: input.roundId },
          select: { configJson: true },
        }),
        ctx.prisma.user.findUniqueOrThrow({
          where: { id: input.userId },
          select: { maxAssignments: true, name: true },
        }),
      ])

      const config = (stage.configJson ?? {}) as Record<string, unknown>
      const maxAssignmentsPerJuror =
        (config.maxLoadPerJuror as number) ??
        (config.maxAssignmentsPerJuror as number) ??
        20
      const effectiveMax = user.maxAssignments ?? maxAssignmentsPerJuror

      const currentCount = await ctx.prisma.assignment.count({
        where: { userId: input.userId, roundId: input.roundId },
      })

      // Check if at or over limit
      if (currentCount >= effectiveMax) {
        if (!input.forceOverride) {
          throw new TRPCError({
            code: 'BAD_REQUEST',
            message: `${user.name || 'Judge'} has reached their maximum limit of ${effectiveMax} projects. Use manual override to proceed.`,
          })
        }
        // Log the override in audit
        console.log(`[Assignment] Manual override: Assigning ${user.name} beyond limit (${currentCount}/${effectiveMax})`)
      }

      const { forceOverride: _override, ...assignmentData } = input
      const assignment = await ctx.prisma.assignment.create({
        data: {
          ...assignmentData,
          method: 'MANUAL',
          createdBy: ctx.user.id,
        },
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'Assignment',
        entityId: assignment.id,
        detailsJson: input,
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      const [project, stageInfo] = await Promise.all([
        ctx.prisma.project.findUnique({
          where: { id: input.projectId },
          select: { title: true },
        }),
        ctx.prisma.round.findUnique({
          where: { id: input.roundId },
          select: { name: true, windowCloseAt: true },
        }),
      ])

      if (project && stageInfo) {
        const deadline = stageInfo.windowCloseAt
          ? new Date(stageInfo.windowCloseAt).toLocaleDateString('en-US', {
              weekday: 'long',
              year: 'numeric',
              month: 'long',
              day: 'numeric',
            })
          : undefined

        await createNotification({
          userId: input.userId,
          type: NotificationTypes.ASSIGNED_TO_PROJECT,
          title: 'New Project Assignment',
          message: `You have been assigned to evaluate "${project.title}" for ${stageInfo.name}.`,
          linkUrl: `/jury/competitions`,
          linkLabel: 'View Assignment',
          metadata: {
            projectName: project.title,
            stageName: stageInfo.name,
            deadline,
            assignmentId: assignment.id,
          },
        })
      }

      return assignment
    }),

  /**
   * Bulk create assignments (admin only)
   */
  bulkCreate: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        assignments: z.array(
          z.object({
            userId: z.string(),
            projectId: z.string(),
          })
        ),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Fetch per-juror maxAssignments and current counts for capacity checking
      const uniqueUserIds = [...new Set(input.assignments.map((a) => a.userId))]
      const users = await ctx.prisma.user.findMany({
        where: { id: { in: uniqueUserIds } },
        select: {
          id: true,
          name: true,
          maxAssignments: true,
          _count: {
            select: {
              assignments: { where: { roundId: input.roundId } },
            },
          },
        },
      })
      const userMap = new Map(users.map((u) => [u.id, u]))

      // Get stage default max
      const stage = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { configJson: true, name: true, windowCloseAt: true },
      })
      const config = (stage.configJson ?? {}) as Record<string, unknown>
      const stageMaxPerJuror =
        (config.maxLoadPerJuror as number) ??
        (config.maxAssignmentsPerJuror as number) ??
        20

      // Track running counts to handle multiple assignments to the same juror in one batch
      const runningCounts = new Map<string, number>()
      for (const u of users) {
        runningCounts.set(u.id, u._count.assignments)
      }

      // Filter out assignments that would exceed a juror's limit
      let skippedDueToCapacity = 0
      const allowedAssignments = input.assignments.filter((a) => {
        const user = userMap.get(a.userId)
        if (!user) return true // unknown user, let createMany handle it

        const effectiveMax = user.maxAssignments ?? stageMaxPerJuror
        const currentCount = runningCounts.get(a.userId) ?? 0

        if (currentCount >= effectiveMax) {
          skippedDueToCapacity++
          return false
        }

        // Increment running count for subsequent assignments to same user
        runningCounts.set(a.userId, currentCount + 1)
        return true
      })

      const result = await ctx.prisma.assignment.createMany({
        data: allowedAssignments.map((a) => ({
          ...a,
          roundId: input.roundId,
          method: 'BULK',
          createdBy: ctx.user.id,
        })),
        skipDuplicates: true,
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'BULK_CREATE',
        entityType: 'Assignment',
        detailsJson: {
          count: result.count,
          requested: input.assignments.length,
          skippedDueToCapacity,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      // Send notifications to assigned jury members (grouped by user)
      if (result.count > 0 && allowedAssignments.length > 0) {
        // Group assignments by user to get counts
        const userAssignmentCounts = allowedAssignments.reduce(
          (acc, a) => {
            acc[a.userId] = (acc[a.userId] || 0) + 1
            return acc
          },
          {} as Record<string, number>
        )

        const deadline = stage?.windowCloseAt
          ? new Date(stage.windowCloseAt).toLocaleDateString('en-US', {
              weekday: 'long',
              year: 'numeric',
              month: 'long',
              day: 'numeric',
            })
          : undefined

        const usersByProjectCount = new Map<number, string[]>()
        for (const [userId, projectCount] of Object.entries(userAssignmentCounts)) {
          const existing = usersByProjectCount.get(projectCount) || []
          existing.push(userId)
          usersByProjectCount.set(projectCount, existing)
        }

        for (const [projectCount, userIds] of usersByProjectCount) {
          if (userIds.length === 0) continue
          await createBulkNotifications({
            userIds,
            type: NotificationTypes.BATCH_ASSIGNED,
            title: `${projectCount} Projects Assigned`,
            message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${stage?.name || 'this stage'}.`,
            linkUrl: `/jury/competitions`,
            linkLabel: 'View Assignments',
            metadata: {
              projectCount,
              stageName: stage?.name,
              deadline,
            },
          })
        }
      }

      return {
        created: result.count,
        requested: input.assignments.length,
        skipped: input.assignments.length - result.count,
        skippedDueToCapacity,
      }
    }),

  /**
   * Delete an assignment (admin only)
   */
  delete: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const assignment = await ctx.prisma.assignment.delete({
        where: { id: input.id },
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'DELETE',
        entityType: 'Assignment',
        entityId: input.id,
        detailsJson: {
          userId: assignment.userId,
          projectId: assignment.projectId,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return assignment
    }),

  /**
   * Get assignment statistics for a round
   */
  getStats: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      const stage = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { configJson: true },
      })
      const config = (stage.configJson ?? {}) as Record<string, unknown>
      const requiredReviews = (config.requiredReviews as number) ?? 3

      const projectRoundStates = await ctx.prisma.projectRoundState.findMany({
        where: { roundId: input.roundId },
        select: { projectId: true },
      })
      const projectIds = projectRoundStates.map((pss) => pss.projectId)

      const [
        totalAssignments,
        completedAssignments,
        assignmentsByUser,
        projectCoverage,
      ] = await Promise.all([
        ctx.prisma.assignment.count({ where: { roundId: input.roundId } }),
        ctx.prisma.assignment.count({
          where: { roundId: input.roundId, isCompleted: true },
        }),
        ctx.prisma.assignment.groupBy({
          by: ['userId'],
          where: { roundId: input.roundId },
          _count: true,
        }),
        ctx.prisma.project.findMany({
          where: { id: { in: projectIds } },
          select: {
            id: true,
            title: true,
            _count: { select: { assignments: { where: { roundId: input.roundId } } } },
          },
        }),
      ])

      const projectsWithFullCoverage = projectCoverage.filter(
        (p) => p._count.assignments >= requiredReviews
      ).length

      return {
        totalAssignments,
        completedAssignments,
        completionPercentage:
          totalAssignments > 0
            ? Math.round((completedAssignments / totalAssignments) * 100)
            : 0,
        juryMembersAssigned: assignmentsByUser.length,
        projectsWithFullCoverage,
        totalProjects: projectCoverage.length,
        coveragePercentage:
          projectCoverage.length > 0
            ? Math.round(
                (projectsWithFullCoverage / projectCoverage.length) * 100
              )
            : 0,
      }
    }),

  /**
   * Get smart assignment suggestions using algorithm
   */
  getSuggestions: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
      })
    )
    .query(async ({ ctx, input }) => {
      const stage = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { configJson: true },
      })
      const config = (stage.configJson ?? {}) as Record<string, unknown>
      const requiredReviews = (config.requiredReviews as number) ?? 3
      const minAssignmentsPerJuror =
        (config.minLoadPerJuror as number) ??
        (config.minAssignmentsPerJuror as number) ??
        1
      const maxAssignmentsPerJuror =
        (config.maxLoadPerJuror as number) ??
        (config.maxAssignmentsPerJuror as number) ??
        20

      // Extract category quotas if enabled
      const categoryQuotasEnabled = config.categoryQuotasEnabled === true
      const categoryQuotas = categoryQuotasEnabled
        ? (config.categoryQuotas as Record<string, { min: number; max: number }> | undefined)
        : undefined

      const jurors = await ctx.prisma.user.findMany({
        where: { role: 'JURY_MEMBER', status: 'ACTIVE' },
        select: {
          id: true,
          name: true,
          email: true,
          expertiseTags: true,
          maxAssignments: true,
          _count: {
            select: {
              assignments: { where: { roundId: input.roundId } },
            },
          },
        },
      })

      const projectRoundStates = await ctx.prisma.projectRoundState.findMany({
        where: { roundId: input.roundId },
        select: { projectId: true },
      })
      const projectIds = projectRoundStates.map((pss) => pss.projectId)

      const projects = await ctx.prisma.project.findMany({
        where: { id: { in: projectIds } },
        select: {
          id: true,
          title: true,
          tags: true,
          competitionCategory: true,
          projectTags: {
            include: { tag: { select: { name: true } } },
          },
          _count: { select: { assignments: { where: { roundId: input.roundId } } } },
        },
      })

      const existingAssignments = await ctx.prisma.assignment.findMany({
        where: { roundId: input.roundId },
        select: { userId: true, projectId: true },
      })
      const assignmentSet = new Set(
        existingAssignments.map((a) => `${a.userId}-${a.projectId}`)
      )

      // Build per-juror category distribution for quota scoring
      const jurorCategoryDistribution = new Map<string, Record<string, number>>()
      if (categoryQuotas) {
        const assignmentsWithCategory = await ctx.prisma.assignment.findMany({
          where: { roundId: input.roundId },
          select: {
            userId: true,
            project: { select: { competitionCategory: true } },
          },
        })
        for (const a of assignmentsWithCategory) {
          const cat = a.project.competitionCategory?.toLowerCase().trim()
          if (!cat) continue
          let catMap = jurorCategoryDistribution.get(a.userId)
          if (!catMap) {
            catMap = {}
            jurorCategoryDistribution.set(a.userId, catMap)
          }
          catMap[cat] = (catMap[cat] || 0) + 1
        }
      }

      const suggestions: Array<{
        userId: string
        jurorName: string
        projectId: string
        projectTitle: string
        score: number
        reasoning: string[]
      }> = []

      for (const project of projects) {
        if (project._count.assignments >= requiredReviews) continue

        const neededAssignments = requiredReviews - project._count.assignments

        const jurorScores = jurors
          .filter((j) => {
            if (assignmentSet.has(`${j.id}-${project.id}`)) return false
            const effectiveMax = j.maxAssignments ?? maxAssignmentsPerJuror
            if (j._count.assignments >= effectiveMax) return false
            return true
          })
          .map((juror) => {
            const reasoning: string[] = []
            let score = 0

            const projectTagNames = project.projectTags.map((pt) => pt.tag.name.toLowerCase())

            const matchingTags = projectTagNames.length > 0
              ? juror.expertiseTags.filter((tag) =>
                  projectTagNames.includes(tag.toLowerCase())
                )
              : juror.expertiseTags.filter((tag) =>
                  project.tags.map((t) => t.toLowerCase()).includes(tag.toLowerCase())
                )

            const totalTags = projectTagNames.length > 0 ? projectTagNames.length : project.tags.length
            const expertiseScore =
              matchingTags.length > 0
                ? matchingTags.length / Math.max(totalTags, 1)
                : 0
            score += expertiseScore * 35
            if (matchingTags.length > 0) {
              reasoning.push(`Expertise match: ${matchingTags.join(', ')}`)
            }

            const effectiveMax = juror.maxAssignments ?? maxAssignmentsPerJuror
            const loadScore = 1 - juror._count.assignments / effectiveMax
            score += loadScore * 20

            const underMinBonus =
              juror._count.assignments < minAssignmentsPerJuror
                ? (minAssignmentsPerJuror - juror._count.assignments) * 3
                : 0
            score += Math.min(15, underMinBonus)

            if (juror._count.assignments < minAssignmentsPerJuror) {
              reasoning.push(
                `Under target: ${juror._count.assignments}/${minAssignmentsPerJuror} min`
              )
            }
            reasoning.push(
              `Capacity: ${juror._count.assignments}/${effectiveMax} max`
            )

            // Category quota scoring
            if (categoryQuotas) {
              const jurorCategoryCounts = jurorCategoryDistribution.get(juror.id) || {}
              const normalizedCat = project.competitionCategory?.toLowerCase().trim()
              if (normalizedCat) {
                const quota = Object.entries(categoryQuotas).find(
                  ([key]) => key.toLowerCase().trim() === normalizedCat
                )
                if (quota) {
                  const [, { min, max }] = quota
                  const currentCount = jurorCategoryCounts[normalizedCat] || 0
                  if (currentCount >= max) {
                    score -= 25
                    reasoning.push(`Category quota exceeded (-25)`)
                  } else if (currentCount < min) {
                    const otherAboveMin = Object.entries(categoryQuotas).some(([key, q]) => {
                      if (key.toLowerCase().trim() === normalizedCat) return false
                      return (jurorCategoryCounts[key.toLowerCase().trim()] || 0) >= q.min
                    })
                    if (otherAboveMin) {
                      score += 10
                      reasoning.push(`Category quota bonus (+10)`)
                    }
                  }
                }
              }
            }

            return {
              userId: juror.id,
              jurorName: juror.name || juror.email || 'Unknown',
              projectId: project.id,
              projectTitle: project.title || 'Unknown',
              score,
              reasoning,
            }
          })
          .sort((a, b) => b.score - a.score)
          .slice(0, neededAssignments)

        suggestions.push(...jurorScores)
      }

      return suggestions.sort((a, b) => b.score - a.score)
    }),

  /**
   * Check if AI assignment is available
   */
  isAIAvailable: adminProcedure.query(async () => {
    return isOpenAIConfigured()
  }),

  /**
   * Get AI-powered assignment suggestions (retrieves from completed job)
   */
  getAISuggestions: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        useAI: z.boolean().default(true),
      })
    )
    .query(async ({ ctx, input }) => {
      const completedJob = await ctx.prisma.assignmentJob.findFirst({
        where: {
          roundId: input.roundId,
          status: 'COMPLETED',
        },
        orderBy: { completedAt: 'desc' },
        select: {
          suggestionsJson: true,
          fallbackUsed: true,
          completedAt: true,
        },
      })

      if (completedJob?.suggestionsJson) {
        const suggestions = completedJob.suggestionsJson as Array<{
          jurorId: string
          jurorName: string
          projectId: string
          projectTitle: string
          confidenceScore: number
          expertiseMatchScore: number
          reasoning: string
        }>

        const existingAssignments = await ctx.prisma.assignment.findMany({
          where: { roundId: input.roundId },
          select: { userId: true, projectId: true },
        })
        const assignmentSet = new Set(
          existingAssignments.map((a) => `${a.userId}-${a.projectId}`)
        )

        const filteredSuggestions = suggestions.filter(
          (s) => !assignmentSet.has(`${s.jurorId}-${s.projectId}`)
        )

        return {
          success: true,
          suggestions: filteredSuggestions,
          fallbackUsed: completedJob.fallbackUsed,
          error: null,
          generatedAt: completedJob.completedAt,
        }
      }

      return {
        success: true,
        suggestions: [],
        fallbackUsed: false,
        error: null,
        generatedAt: null,
      }
    }),

  /**
   * Apply AI-suggested assignments
   */
  applyAISuggestions: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        assignments: z.array(
          z.object({
            userId: z.string(),
            projectId: z.string(),
            confidenceScore: z.number().optional(),
            expertiseMatchScore: z.number().optional(),
            reasoning: z.string().optional(),
          })
        ),
        usedAI: z.boolean().default(false),
        forceOverride: z.boolean().default(false),
      })
    )
    .mutation(async ({ ctx, input }) => {
      let assignmentsToCreate = input.assignments
      let skippedDueToCapacity = 0

      // Capacity check (unless forceOverride)
      if (!input.forceOverride) {
        const uniqueUserIds = [...new Set(input.assignments.map((a) => a.userId))]
        const users = await ctx.prisma.user.findMany({
          where: { id: { in: uniqueUserIds } },
          select: {
            id: true,
            maxAssignments: true,
            _count: {
              select: {
                assignments: { where: { roundId: input.roundId } },
              },
            },
          },
        })
        const userMap = new Map(users.map((u) => [u.id, u]))

        const stageData = await ctx.prisma.round.findUniqueOrThrow({
          where: { id: input.roundId },
          select: { configJson: true },
        })
        const config = (stageData.configJson ?? {}) as Record<string, unknown>
        const stageMaxPerJuror =
          (config.maxLoadPerJuror as number) ??
          (config.maxAssignmentsPerJuror as number) ??
          20

        const runningCounts = new Map<string, number>()
        for (const u of users) {
          runningCounts.set(u.id, u._count.assignments)
        }

        assignmentsToCreate = input.assignments.filter((a) => {
          const user = userMap.get(a.userId)
          if (!user) return true

          const effectiveMax = user.maxAssignments ?? stageMaxPerJuror
          const currentCount = runningCounts.get(a.userId) ?? 0

          if (currentCount >= effectiveMax) {
            skippedDueToCapacity++
            return false
          }

          runningCounts.set(a.userId, currentCount + 1)
          return true
        })
      }

      const created = await ctx.prisma.assignment.createMany({
        data: assignmentsToCreate.map((a) => ({
          userId: a.userId,
          projectId: a.projectId,
          roundId: input.roundId,
          method: input.usedAI ? 'AI_SUGGESTED' : 'ALGORITHM',
          aiConfidenceScore: a.confidenceScore,
          expertiseMatchScore: a.expertiseMatchScore,
          aiReasoning: a.reasoning,
          createdBy: ctx.user.id,
        })),
        skipDuplicates: true,
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: input.usedAI ? 'APPLY_AI_SUGGESTIONS' : 'APPLY_SUGGESTIONS',
        entityType: 'Assignment',
        detailsJson: {
          roundId: input.roundId,
          count: created.count,
          usedAI: input.usedAI,
          forceOverride: input.forceOverride,
          skippedDueToCapacity,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      if (created.count > 0) {
        const userAssignmentCounts = assignmentsToCreate.reduce(
          (acc, a) => {
            acc[a.userId] = (acc[a.userId] || 0) + 1
            return acc
          },
          {} as Record<string, number>
        )

        const stage = await ctx.prisma.round.findUnique({
          where: { id: input.roundId },
          select: { name: true, windowCloseAt: true },
        })

        const deadline = stage?.windowCloseAt
          ? new Date(stage.windowCloseAt).toLocaleDateString('en-US', {
              weekday: 'long',
              year: 'numeric',
              month: 'long',
              day: 'numeric',
            })
          : undefined

        const usersByProjectCount = new Map<number, string[]>()
        for (const [userId, projectCount] of Object.entries(userAssignmentCounts)) {
          const existing = usersByProjectCount.get(projectCount) || []
          existing.push(userId)
          usersByProjectCount.set(projectCount, existing)
        }

        for (const [projectCount, userIds] of usersByProjectCount) {
          if (userIds.length === 0) continue
          await createBulkNotifications({
            userIds,
            type: NotificationTypes.BATCH_ASSIGNED,
            title: `${projectCount} Projects Assigned`,
            message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${stage?.name || 'this stage'}.`,
            linkUrl: `/jury/competitions`,
            linkLabel: 'View Assignments',
            metadata: {
              projectCount,
              stageName: stage?.name,
              deadline,
            },
          })
        }
      }

      return {
        created: created.count,
        requested: input.assignments.length,
        skippedDueToCapacity,
      }
    }),

  /**
   * Apply suggested assignments
   */
  applySuggestions: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        assignments: z.array(
          z.object({
            userId: z.string(),
            projectId: z.string(),
            reasoning: z.string().optional(),
          })
        ),
        forceOverride: z.boolean().default(false),
      })
    )
    .mutation(async ({ ctx, input }) => {
      let assignmentsToCreate = input.assignments
      let skippedDueToCapacity = 0

      // Capacity check (unless forceOverride)
      if (!input.forceOverride) {
        const uniqueUserIds = [...new Set(input.assignments.map((a) => a.userId))]
        const users = await ctx.prisma.user.findMany({
          where: { id: { in: uniqueUserIds } },
          select: {
            id: true,
            maxAssignments: true,
            _count: {
              select: {
                assignments: { where: { roundId: input.roundId } },
              },
            },
          },
        })
        const userMap = new Map(users.map((u) => [u.id, u]))

        const stageData = await ctx.prisma.round.findUniqueOrThrow({
          where: { id: input.roundId },
          select: { configJson: true },
        })
        const config = (stageData.configJson ?? {}) as Record<string, unknown>
        const stageMaxPerJuror =
          (config.maxLoadPerJuror as number) ??
          (config.maxAssignmentsPerJuror as number) ??
          20

        const runningCounts = new Map<string, number>()
        for (const u of users) {
          runningCounts.set(u.id, u._count.assignments)
        }

        assignmentsToCreate = input.assignments.filter((a) => {
          const user = userMap.get(a.userId)
          if (!user) return true

          const effectiveMax = user.maxAssignments ?? stageMaxPerJuror
          const currentCount = runningCounts.get(a.userId) ?? 0

          if (currentCount >= effectiveMax) {
            skippedDueToCapacity++
            return false
          }

          runningCounts.set(a.userId, currentCount + 1)
          return true
        })
      }

      const created = await ctx.prisma.assignment.createMany({
        data: assignmentsToCreate.map((a) => ({
          userId: a.userId,
          projectId: a.projectId,
          roundId: input.roundId,
          method: 'ALGORITHM',
          aiReasoning: a.reasoning,
          createdBy: ctx.user.id,
        })),
        skipDuplicates: true,
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'APPLY_SUGGESTIONS',
        entityType: 'Assignment',
        detailsJson: {
          roundId: input.roundId,
          count: created.count,
          forceOverride: input.forceOverride,
          skippedDueToCapacity,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      if (created.count > 0) {
        const userAssignmentCounts = assignmentsToCreate.reduce(
          (acc, a) => {
            acc[a.userId] = (acc[a.userId] || 0) + 1
            return acc
          },
          {} as Record<string, number>
        )

        const stage = await ctx.prisma.round.findUnique({
          where: { id: input.roundId },
          select: { name: true, windowCloseAt: true },
        })

        const deadline = stage?.windowCloseAt
          ? new Date(stage.windowCloseAt).toLocaleDateString('en-US', {
              weekday: 'long',
              year: 'numeric',
              month: 'long',
              day: 'numeric',
            })
          : undefined

        const usersByProjectCount = new Map<number, string[]>()
        for (const [userId, projectCount] of Object.entries(userAssignmentCounts)) {
          const existing = usersByProjectCount.get(projectCount) || []
          existing.push(userId)
          usersByProjectCount.set(projectCount, existing)
        }

        for (const [projectCount, userIds] of usersByProjectCount) {
          if (userIds.length === 0) continue
          await createBulkNotifications({
            userIds,
            type: NotificationTypes.BATCH_ASSIGNED,
            title: `${projectCount} Projects Assigned`,
            message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${stage?.name || 'this stage'}.`,
            linkUrl: `/jury/competitions`,
            linkLabel: 'View Assignments',
            metadata: {
              projectCount,
              stageName: stage?.name,
              deadline,
            },
          })
        }
      }

      return {
        created: created.count,
        requested: input.assignments.length,
        skippedDueToCapacity,
      }
    }),

  /**
   * Start an AI assignment job (background processing)
   */
  startAIAssignmentJob: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const existingJob = await ctx.prisma.assignmentJob.findFirst({
        where: {
          roundId: input.roundId,
          status: { in: ['PENDING', 'RUNNING'] },
        },
      })

      if (existingJob) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'An AI assignment job is already running for this stage',
        })
      }

      if (!isOpenAIConfigured()) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'OpenAI API is not configured',
        })
      }

      const job = await ctx.prisma.assignmentJob.create({
        data: {
          roundId: input.roundId,
          status: 'PENDING',
        },
      })

      runAIAssignmentJob(job.id, input.roundId, ctx.user.id).catch(console.error)

      return { jobId: job.id }
    }),

  /**
   * Get AI assignment job status (for polling)
   */
  getAIAssignmentJobStatus: adminProcedure
    .input(z.object({ jobId: z.string() }))
    .query(async ({ ctx, input }) => {
      const job = await ctx.prisma.assignmentJob.findUniqueOrThrow({
        where: { id: input.jobId },
      })

      return {
        id: job.id,
        status: job.status,
        totalProjects: job.totalProjects,
        totalBatches: job.totalBatches,
        currentBatch: job.currentBatch,
        processedCount: job.processedCount,
        suggestionsCount: job.suggestionsCount,
        fallbackUsed: job.fallbackUsed,
        errorMessage: job.errorMessage,
        startedAt: job.startedAt,
        completedAt: job.completedAt,
      }
    }),

  /**
   * Get the latest AI assignment job for a round
   */
  getLatestAIAssignmentJob: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      const job = await ctx.prisma.assignmentJob.findFirst({
        where: { roundId: input.roundId },
        orderBy: { createdAt: 'desc' },
      })

      if (!job) return null

      return {
        id: job.id,
        status: job.status,
        totalProjects: job.totalProjects,
        totalBatches: job.totalBatches,
        currentBatch: job.currentBatch,
        processedCount: job.processedCount,
        suggestionsCount: job.suggestionsCount,
        fallbackUsed: job.fallbackUsed,
        errorMessage: job.errorMessage,
        startedAt: job.startedAt,
        completedAt: job.completedAt,
        createdAt: job.createdAt,
      }
    }),
})
