import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, adminProcedure, protectedProcedure, juryProcedure } from '../trpc'
import {
  previewRoundAssignment,
  executeRoundAssignment,
  getRoundCoverageReport,
  getUnassignedQueue,
} from '../services/round-assignment'
import { generateAIAssignments } from '../services/ai-assignment'

export const roundAssignmentRouter = router({
  /**
   * AI-powered assignment preview using GPT with enriched project/juror data
   */
  aiPreview: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        requiredReviews: z.number().int().min(1).max(20).default(3),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Load round with jury group
      const round = await ctx.prisma.round.findUnique({
        where: { id: input.roundId },
        include: {
          juryGroup: {
            include: {
              members: {
                include: {
                  user: {
                    select: {
                      id: true, name: true, email: true,
                      bio: true, expertiseTags: true, country: true,
                    },
                  },
                },
              },
            },
          },
        },
      })

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

      if (!round.juryGroup) {
        return {
          assignments: [],
          warnings: ['Round has no linked jury group'],
          stats: { totalProjects: 0, totalJurors: 0, assignmentsGenerated: 0, unassignedProjects: 0 },
          fallbackUsed: false,
          tokensUsed: 0,
        }
      }

      // Load projects with rich data (descriptions, tags, files, team members, etc.)
      const projectStates = await ctx.prisma.projectRoundState.findMany({
        where: { roundId: input.roundId, state: { in: ['PENDING', 'IN_PROGRESS'] } },
        include: {
          project: {
            include: {
              projectTags: { include: { tag: true } },
              files: { select: { fileType: true, size: true, pageCount: true } },
              _count: { select: { teamMembers: true } },
            },
          },
        },
      })

      if (projectStates.length === 0) {
        return {
          assignments: [],
          warnings: ['No active projects in this round'],
          stats: { totalProjects: 0, totalJurors: round.juryGroup.members.length, assignmentsGenerated: 0, unassignedProjects: 0 },
          fallbackUsed: false,
          tokensUsed: 0,
        }
      }

      // Load existing assignments
      const existingAssignments = await ctx.prisma.assignment.findMany({
        where: { roundId: input.roundId },
        select: { userId: true, projectId: true },
      })

      // Load COI records to exclude
      const coiRecords = await ctx.prisma.conflictOfInterest.findMany({
        where: { assignment: { roundId: input.roundId }, hasConflict: true },
        select: { userId: true, projectId: true },
      })
      const coiPairs = new Set(coiRecords.map((c: { userId: string; projectId: string }) => `${c.userId}:${c.projectId}`))

      // Build enriched juror data for AI
      const jurors = round.juryGroup.members.map((m) => ({
        id: m.user.id,
        name: m.user.name,
        email: m.user.email,
        expertiseTags: (m.user.expertiseTags as string[]) ?? [],
        bio: m.user.bio as string | null,
        country: m.user.country as string | null,
        maxAssignments: m.maxAssignmentsOverride ?? null,
        _count: {
          assignments: existingAssignments.filter((a) => a.userId === m.user.id).length,
        },
      }))

      // Build enriched project data for AI
      const projects = projectStates.map((ps) => {
        const p = ps.project as any
        return {
          id: p.id as string,
          title: p.title as string,
          description: p.description as string | null,
          tags: (p.projectTags?.map((pt: any) => pt.tag?.name).filter(Boolean) ?? p.tags ?? []) as string[],
          tagConfidences: p.projectTags?.map((pt: any) => ({
            name: pt.tag?.name as string,
            confidence: (pt.confidence as number) ?? 1.0,
          })) as Array<{ name: string; confidence: number }> | undefined,
          teamName: p.teamName as string | null,
          competitionCategory: p.competitionCategory as string | null,
          oceanIssue: p.oceanIssue as string | null,
          country: p.country as string | null,
          institution: p.institution as string | null,
          teamSize: (p._count?.teamMembers as number) ?? 0,
          fileTypes: (p.files?.map((f: any) => f.fileType).filter(Boolean) ?? []) as string[],
          _count: {
            assignments: existingAssignments.filter((a) => a.projectId === p.id).length,
          },
        }
      })

      // Build constraints
      const configJson = round.configJson as Record<string, unknown> | null
      const configuredMax = (configJson?.maxAssignmentsPerJuror as number) ?? undefined

      // If no explicit cap, calculate a balanced one: ceil(total_needed / juror_count) + 2 buffer
      const totalNeeded = projectStates.length * input.requiredReviews
      const jurorCount = round.juryGroup.members.length
      const calculatedMax = Math.ceil(totalNeeded / jurorCount) + 2
      const maxPerJuror = configuredMax ?? calculatedMax

      // Build per-juror cap overrides
      const jurorLimits: Record<string, number> = {}
      for (const m of round.juryGroup.members) {
        if (m.maxAssignmentsOverride != null) {
          jurorLimits[m.user.id] = m.maxAssignmentsOverride
        }
      }

      const constraints = {
        requiredReviewsPerProject: input.requiredReviews,
        maxAssignmentsPerJuror: maxPerJuror,
        jurorLimits,
        existingAssignments: existingAssignments.map((a) => ({
          jurorId: a.userId,
          projectId: a.projectId,
        })),
      }

      // Call AI service
      console.log(`[AI Assignment Router] Starting for ${projects.length} projects, ${jurors.length} jurors, ${input.requiredReviews} reviews/project, max ${maxPerJuror}/juror`)
      const result = await generateAIAssignments(
        jurors,
        projects,
        constraints,
        ctx.user.id,
        input.roundId,
      )
      console.log(`[AI Assignment Router] Got ${result.suggestions.length} suggestions, success=${result.success}, fallback=${result.fallbackUsed}`)

      // Filter out COI pairs and already-assigned pairs
      const existingPairSet = new Set(existingAssignments.map((a) => `${a.userId}:${a.projectId}`))
      const filteredSuggestions = result.suggestions.filter((s) =>
        !coiPairs.has(`${s.jurorId}:${s.projectId}`) &&
        !existingPairSet.has(`${s.jurorId}:${s.projectId}`)
      )

      // Map to common AssignmentPreview format
      const jurorNameMap = new Map(jurors.map((j) => [j.id, j.name ?? 'Unknown']))
      const projectTitleMap = new Map(projects.map((p) => [p.id, p.title]))

      const assignments = filteredSuggestions.map((s) => ({
        userId: s.jurorId,
        userName: jurorNameMap.get(s.jurorId) ?? 'Unknown',
        projectId: s.projectId,
        projectTitle: projectTitleMap.get(s.projectId) ?? 'Unknown',
        score: Math.round(s.confidenceScore * 100),
        breakdown: {
          tagOverlap: Math.round(s.expertiseMatchScore * 100),
          bioMatch: 0,
          workloadBalance: 0,
          countryMatch: 0,
          geoDiversityPenalty: 0,
          previousRoundFamiliarity: 0,
          coiPenalty: 0,
          availabilityPenalty: 0,
          categoryQuotaPenalty: 0,
        },
        reasoning: [s.reasoning],
        matchingTags: [] as string[],
        policyViolations: [] as string[],
        fromIntent: false,
      }))

      const assignedProjectIds = new Set(assignments.map((a) => a.projectId))

      // Warn about jurors without profile data
      const warnings: string[] = result.error ? [result.error] : []
      const incompleteJurors = jurors.filter(
        (j) => (!j.expertiseTags || j.expertiseTags.length === 0) && !j.bio
      )
      if (incompleteJurors.length > 0) {
        const names = incompleteJurors.map((j) => j.name || 'Unknown').join(', ')
        warnings.push(
          `${incompleteJurors.length} juror(s) have no expertise tags or bio (${names}). Their assignments are based on workload balance only — consider asking them to complete their profile first.`
        )
      }

      return {
        assignments,
        warnings,
        stats: {
          totalProjects: projects.length,
          totalJurors: jurors.length,
          assignmentsGenerated: assignments.length,
          unassignedProjects: projects.length - assignedProjectIds.size,
        },
        fallbackUsed: result.fallbackUsed ?? false,
        tokensUsed: result.tokensUsed ?? 0,
      }
    }),

  /**
   * Preview round assignments without committing (algorithmic)
   */
  preview: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        honorIntents: z.boolean().default(true),
        requiredReviews: z.number().int().min(1).max(20).default(3),
      })
    )
    .query(async ({ ctx, input }) => {
      return previewRoundAssignment(
        input.roundId,
        {
          honorIntents: input.honorIntents,
          requiredReviews: input.requiredReviews,
        },
        ctx.prisma,
      )
    }),

  /**
   * Execute round assignments (create Assignment records)
   */
  execute: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        assignments: z.array(
          z.object({
            userId: z.string(),
            projectId: z.string(),
          })
        ).min(1),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const result = await executeRoundAssignment(
        input.roundId,
        input.assignments,
        ctx.user.id,
        ctx.prisma,
      )

      if (result.errors.length > 0 && result.created === 0) {
        throw new TRPCError({
          code: 'INTERNAL_SERVER_ERROR',
          message: result.errors.join('; '),
        })
      }

      return result
    }),

  /**
   * Get coverage report for a round
   */
  coverageReport: protectedProcedure
    .input(
      z.object({
        roundId: z.string(),
        requiredReviews: z.number().int().min(1).max(20).default(3),
      })
    )
    .query(async ({ ctx, input }) => {
      return getRoundCoverageReport(input.roundId, input.requiredReviews, ctx.prisma)
    }),

  /**
   * Get projects below required reviews threshold
   */
  unassignedQueue: protectedProcedure
    .input(
      z.object({
        roundId: z.string(),
        requiredReviews: z.number().int().min(1).max(20).default(3),
      })
    )
    .query(async ({ ctx, input }) => {
      return getUnassignedQueue(input.roundId, input.requiredReviews, ctx.prisma)
    }),

  /**
   * Get assignments for the current jury member in a specific round
   */
  getMyAssignments: juryProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.assignment.findMany({
        where: {
          roundId: input.roundId,
          userId: ctx.user.id,
        },
        include: {
          project: {
            select: { id: true, title: true, competitionCategory: true },
          },
          evaluation: {
            select: { id: true, status: true, globalScore: true },
          },
        },
        orderBy: { createdAt: 'asc' },
      })
    }),
})
