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

export const assignmentRouter = router({
  /**
   * List assignments for a round (admin only)
   */
  listByRound: 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: '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),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Check if assignment already exists
      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',
        })
      }

      // Check user's assignment limit
      const user = await ctx.prisma.user.findUniqueOrThrow({
        where: { id: input.userId },
        select: { maxAssignments: true },
      })

      if (user.maxAssignments !== null) {
        const currentCount = await ctx.prisma.assignment.count({
          where: { userId: input.userId, roundId: input.roundId },
        })

        if (currentCount >= user.maxAssignments) {
          throw new TRPCError({
            code: 'BAD_REQUEST',
            message: `User has reached their maximum assignment limit of ${user.maxAssignments}`,
          })
        }
      }

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

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

      // Send notification to the assigned jury member
      const [project, round] = 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, votingEndAt: true },
        }),
      ])

      if (project && round) {
        const deadline = round.votingEndAt
          ? new Date(round.votingEndAt).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 ${round.name}.`,
          linkUrl: `/jury/assignments`,
          linkLabel: 'View Assignment',
          metadata: {
            projectName: project.title,
            roundName: round.name,
            deadline,
            assignmentId: assignment.id,
          },
        })
      }

      return assignment
    }),

  /**
   * Bulk create assignments (admin only)
   */
  bulkCreate: adminProcedure
    .input(
      z.object({
        assignments: z.array(
          z.object({
            userId: z.string(),
            projectId: z.string(),
            roundId: z.string(),
          })
        ),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const result = await ctx.prisma.assignment.createMany({
        data: input.assignments.map((a) => ({
          ...a,
          method: 'BULK',
          createdBy: ctx.user.id,
        })),
        skipDuplicates: true,
      })

      // Audit log
      await ctx.prisma.auditLog.create({
        data: {
          userId: ctx.user.id,
          action: 'BULK_CREATE',
          entityType: 'Assignment',
          detailsJson: { count: result.count },
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        },
      })

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

        // Get round info for deadline
        const roundId = input.assignments[0].roundId
        const round = await ctx.prisma.round.findUnique({
          where: { id: roundId },
          select: { name: true, votingEndAt: true },
        })

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

        // Send batch notification to each user
        for (const [userId, projectCount] of Object.entries(userAssignmentCounts)) {
          await createNotification({
            userId,
            type: NotificationTypes.BATCH_ASSIGNED,
            title: `${projectCount} Projects Assigned`,
            message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${round?.name || 'this round'}.`,
            linkUrl: `/jury/assignments`,
            linkLabel: 'View Assignments',
            metadata: {
              projectCount,
              roundName: round?.name,
              deadline,
            },
          })
        }
      }

      return { created: result.count }
    }),

  /**
   * 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 ctx.prisma.auditLog.create({
        data: {
          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 [
        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: { roundId: input.roundId },
          select: {
            id: true,
            title: true,
            _count: { select: { assignments: true } },
          },
        }),
      ])

      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { requiredReviews: true },
      })

      const projectsWithFullCoverage = projectCoverage.filter(
        (p) => p._count.assignments >= round.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(),
        maxPerJuror: z.number().int().min(1).max(50).default(10),
        minPerProject: z.number().int().min(1).max(10).default(3),
      })
    )
    .query(async ({ ctx, input }) => {
      // Get all active jury members with their expertise and current load
      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 } },
            },
          },
        },
      })

      // Get all projects that need more assignments
      const projects = await ctx.prisma.project.findMany({
        where: { roundId: input.roundId },
        select: {
          id: true,
          title: true,
          tags: true,
          _count: { select: { assignments: true } },
        },
      })

      // Get existing assignments to avoid duplicates
      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}`)
      )

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

      for (const project of projects) {
        // Skip if project has enough assignments
        if (project._count.assignments >= input.minPerProject) continue

        const neededAssignments = input.minPerProject - project._count.assignments

        // Score each juror for this project
        const jurorScores = jurors
          .filter((j) => {
            // Skip if already assigned
            if (assignmentSet.has(`${j.id}-${project.id}`)) return false
            // Skip if at max capacity
            const maxAllowed = j.maxAssignments ?? input.maxPerJuror
            if (j._count.assignments >= maxAllowed) return false
            return true
          })
          .map((juror) => {
            const reasoning: string[] = []
            let score = 0

            // Expertise match (40% weight)
            const matchingTags = juror.expertiseTags.filter((tag) =>
              project.tags.includes(tag)
            )
            const expertiseScore =
              matchingTags.length > 0
                ? matchingTags.length / Math.max(project.tags.length, 1)
                : 0
            score += expertiseScore * 40
            if (matchingTags.length > 0) {
              reasoning.push(`Expertise match: ${matchingTags.join(', ')}`)
            }

            // Load balancing (25% weight)
            const maxAllowed = juror.maxAssignments ?? input.maxPerJuror
            const loadScore = 1 - juror._count.assignments / maxAllowed
            score += loadScore * 25
            reasoning.push(
              `Workload: ${juror._count.assignments}/${maxAllowed} assigned`
            )

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

      // Sort by score and return
      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
   */
  getAISuggestions: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        useAI: z.boolean().default(true),
        maxPerJuror: z.number().int().min(1).max(50).default(10),
      })
    )
    .query(async ({ ctx, input }) => {
      // Get round info
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { requiredReviews: true },
      })

      // Get all active jury members with their expertise and current load
      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 } },
            },
          },
        },
      })

      // Get all projects in the round
      const projects = await ctx.prisma.project.findMany({
        where: { roundId: input.roundId },
        select: {
          id: true,
          title: true,
          description: true,
          tags: true,
          teamName: true,
          _count: { select: { assignments: true } },
        },
      })

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

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

      // Use AI or fallback based on input and availability
      let result
      if (input.useAI) {
        result = await generateAIAssignments(jurors, projects, constraints)
      } else {
        result = generateFallbackAssignments(jurors, projects, constraints)
      }

      // Enrich suggestions with user and project names for display
      const enrichedSuggestions = await Promise.all(
        result.suggestions.map(async (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',
          }
        })
      )

      return {
        success: result.success,
        suggestions: enrichedSuggestions,
        fallbackUsed: result.fallbackUsed,
        error: result.error,
      }
    }),

  /**
   * 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),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const created = await ctx.prisma.assignment.createMany({
        data: input.assignments.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,
      })

      // Audit log
      await ctx.prisma.auditLog.create({
        data: {
          userId: ctx.user.id,
          action: input.usedAI ? 'APPLY_AI_SUGGESTIONS' : 'APPLY_SUGGESTIONS',
          entityType: 'Assignment',
          detailsJson: {
            roundId: input.roundId,
            count: created.count,
            usedAI: input.usedAI,
          },
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        },
      })

      // Send notifications to assigned jury members
      if (created.count > 0) {
        const userAssignmentCounts = input.assignments.reduce(
          (acc, a) => {
            acc[a.userId] = (acc[a.userId] || 0) + 1
            return acc
          },
          {} as Record<string, number>
        )

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

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

        for (const [userId, projectCount] of Object.entries(userAssignmentCounts)) {
          await createNotification({
            userId,
            type: NotificationTypes.BATCH_ASSIGNED,
            title: `${projectCount} Projects Assigned`,
            message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${round?.name || 'this round'}.`,
            linkUrl: `/jury/assignments`,
            linkLabel: 'View Assignments',
            metadata: {
              projectCount,
              roundName: round?.name,
              deadline,
            },
          })
        }
      }

      return { created: created.count }
    }),

  /**
   * 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(),
          })
        ),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const created = await ctx.prisma.assignment.createMany({
        data: input.assignments.map((a) => ({
          userId: a.userId,
          projectId: a.projectId,
          roundId: input.roundId,
          method: 'ALGORITHM',
          aiReasoning: a.reasoning,
          createdBy: ctx.user.id,
        })),
        skipDuplicates: true,
      })

      // Audit log
      await ctx.prisma.auditLog.create({
        data: {
          userId: ctx.user.id,
          action: 'APPLY_SUGGESTIONS',
          entityType: 'Assignment',
          detailsJson: {
            roundId: input.roundId,
            count: created.count,
          },
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        },
      })

      // Send notifications to assigned jury members
      if (created.count > 0) {
        const userAssignmentCounts = input.assignments.reduce(
          (acc, a) => {
            acc[a.userId] = (acc[a.userId] || 0) + 1
            return acc
          },
          {} as Record<string, number>
        )

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

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

        for (const [userId, projectCount] of Object.entries(userAssignmentCounts)) {
          await createNotification({
            userId,
            type: NotificationTypes.BATCH_ASSIGNED,
            title: `${projectCount} Projects Assigned`,
            message: `You have been assigned ${projectCount} project${projectCount > 1 ? 's' : ''} to evaluate for ${round?.name || 'this round'}.`,
            linkUrl: `/jury/assignments`,
            linkLabel: 'View Assignments',
            metadata: {
              projectCount,
              roundName: round?.name,
              deadline,
            },
          })
        }
      }

      return { created: created.count }
    }),
})
