import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, adminProcedure } from '../trpc'
import { logAudit } from '../utils/audit'

/**
 * Project Pool Router
 *
 * Manages the project pool for assigning projects to competition rounds.
 * Shows all projects by default, with optional filtering for unassigned-only
 * or projects not yet in a specific round.
 */
export const projectPoolRouter = router({
  /**
   * List projects in the pool with filtering and pagination.
   * By default shows ALL projects. Use filters to narrow:
   *   - unassignedOnly: true → only projects not in any round
   *   - excludeRoundId: "..." → only projects not already in that round
   */
  listUnassigned: adminProcedure
    .input(
      z.object({
        programId: z.string(),
        competitionCategory: z
          .enum(['STARTUP', 'BUSINESS_CONCEPT'])
          .optional(),
        search: z.string().optional(),
        unassignedOnly: z.boolean().optional().default(false),
        excludeRoundId: z.string().optional(),
        page: z.number().int().min(1).default(1),
        perPage: z.number().int().min(1).max(200).default(50),
      })
    )
    .query(async ({ ctx, input }) => {
      const { programId, competitionCategory, search, unassignedOnly, excludeRoundId, page, perPage } = input
      const skip = (page - 1) * perPage

      // Build where clause
      const where: Record<string, unknown> = {
        programId,
      }

      // Optional: only show projects not in any round
      if (unassignedOnly) {
        where.projectRoundStates = { none: {} }
      }

      // Optional: exclude projects already in a specific round
      if (excludeRoundId && !unassignedOnly) {
        where.projectRoundStates = {
          none: { roundId: excludeRoundId },
        }
      }

      // Filter by competition category
      if (competitionCategory) {
        where.competitionCategory = competitionCategory
      }

      // Search in title, teamName, description, institution, country, geographicZone, team member names/emails
      if (search) {
        where.OR = [
          { title: { contains: search, mode: 'insensitive' } },
          { teamName: { contains: search, mode: 'insensitive' } },
          { description: { contains: search, mode: 'insensitive' } },
          { institution: { contains: search, mode: 'insensitive' } },
          { country: { contains: search, mode: 'insensitive' } },
          { geographicZone: { contains: search, mode: 'insensitive' } },
          { teamMembers: { some: { user: { name: { contains: search, mode: 'insensitive' } } } } },
          { teamMembers: { some: { user: { email: { contains: search, mode: 'insensitive' } } } } },
        ]
      }

      // Execute queries in parallel
      const [projects, total] = await Promise.all([
        ctx.prisma.project.findMany({
          where,
          skip,
          take: perPage,
          orderBy: { createdAt: 'desc' },
          select: {
            id: true,
            title: true,
            teamName: true,
            description: true,
            competitionCategory: true,
            oceanIssue: true,
            country: true,
            status: true,
            submittedAt: true,
            createdAt: true,
            tags: true,
            wantsMentorship: true,
            programId: true,
            _count: {
              select: {
                files: true,
                teamMembers: true,
              },
            },
            projectRoundStates: {
              select: {
                roundId: true,
                state: true,
                round: {
                  select: {
                    name: true,
                    roundType: true,
                    sortOrder: true,
                  },
                },
              },
              orderBy: {
                round: { sortOrder: 'asc' },
              },
            },
          },
        }),
        ctx.prisma.project.count({ where }),
      ])

      return {
        projects,
        total,
        page,
        perPage,
        totalPages: Math.ceil(total / perPage),
      }
    }),

  /**
   * Bulk assign projects to a round
   */
  assignToRound: adminProcedure
    .input(
      z.object({
        projectIds: z.array(z.string()).min(1).max(1000),
        roundId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { projectIds, roundId } = input

      // Step 1: Fetch all projects to validate
      const projects = await ctx.prisma.project.findMany({
        where: {
          id: { in: projectIds },
        },
        select: {
          id: true,
          title: true,
          programId: true,
        },
      })

      // Validate all projects were found
      if (projects.length !== projectIds.length) {
        const foundIds = new Set(projects.map((p) => p.id))
        const missingIds = projectIds.filter((id) => !foundIds.has(id))
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: `Some projects were not found: ${missingIds.join(', ')}`,
        })
      }

      // Verify round exists
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: roundId },
        select: { id: true, name: true },
      })

      // Step 2: Perform bulk assignment in a transaction
      const result = await ctx.prisma.$transaction(async (tx) => {
        // Create ProjectRoundState entries for each project (skip existing)
        const assignmentData = projectIds.map((projectId) => ({
          projectId,
          roundId,
        }))

        await tx.projectRoundState.createMany({
          data: assignmentData,
          skipDuplicates: true,
        })

        // Update project statuses
        const updatedProjects = await tx.project.updateMany({
          where: {
            id: { in: projectIds },
          },
          data: {
            status: 'ASSIGNED',
          },
        })

        // Create status history records for each project
        await tx.projectStatusHistory.createMany({
          data: projectIds.map((projectId) => ({
            projectId,
            status: 'ASSIGNED',
            changedBy: ctx.user?.id,
          })),
        })

        return updatedProjects
      })

      // Audit outside transaction so failures don't roll back the assignment
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user?.id,
        action: 'BULK_ASSIGN_TO_ROUND',
        entityType: 'Project',
        detailsJson: {
          roundId,
          projectCount: projectIds.length,
          projectIds,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return {
        success: true,
        assignedCount: result.count,
        roundId,
      }
    }),

  /**
   * Assign ALL matching projects in a program to a round (server-side, no ID limit).
   * Skips projects already in the target round.
   */
  assignAllToRound: adminProcedure
    .input(
      z.object({
        programId: z.string(),
        roundId: z.string(),
        competitionCategory: z.enum(['STARTUP', 'BUSINESS_CONCEPT']).optional(),
        unassignedOnly: z.boolean().optional().default(false),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { programId, roundId, competitionCategory, unassignedOnly } = input

      // Verify round exists
      await ctx.prisma.round.findUniqueOrThrow({
        where: { id: roundId },
        select: { id: true },
      })

      // Find projects to assign
      const where: Record<string, unknown> = {
        programId,
      }

      if (unassignedOnly) {
        // Only projects not in any round
        where.projectRoundStates = { none: {} }
      } else {
        // All projects not already in the target round
        where.projectRoundStates = {
          none: { roundId },
        }
      }

      if (competitionCategory) {
        where.competitionCategory = competitionCategory
      }

      const projects = await ctx.prisma.project.findMany({
        where,
        select: { id: true },
      })

      if (projects.length === 0) {
        return { success: true, assignedCount: 0, roundId }
      }

      const projectIds = projects.map((p) => p.id)

      const result = await ctx.prisma.$transaction(async (tx) => {
        await tx.projectRoundState.createMany({
          data: projectIds.map((projectId) => ({ projectId, roundId })),
          skipDuplicates: true,
        })

        const updated = await tx.project.updateMany({
          where: { id: { in: projectIds } },
          data: { status: 'ASSIGNED' },
        })

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

        return updated
      })

      // Audit outside transaction so failures don't roll back the assignment
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user?.id,
        action: 'BULK_ASSIGN_ALL_TO_ROUND',
        entityType: 'Project',
        detailsJson: {
          roundId,
          programId,
          competitionCategory: competitionCategory || 'ALL',
          unassignedOnly,
          projectCount: projectIds.length,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return { success: true, assignedCount: result.count, roundId }
    }),

  /**
   * List projects in a specific round (for import-from-round picker).
   */
  getProjectsInRound: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        states: z.array(z.string()).optional(),
        search: z.string().optional(),
      })
    )
    .query(async ({ ctx, input }) => {
      const { roundId, states, search } = input

      const where: Record<string, unknown> = { roundId }

      if (states && states.length > 0) {
        where.state = { in: states }
      }

      if (search?.trim()) {
        where.project = {
          OR: [
            { title: { contains: search, mode: 'insensitive' } },
            { teamName: { contains: search, mode: 'insensitive' } },
            { teamMembers: { some: { user: { name: { contains: search, mode: 'insensitive' } } } } },
            { teamMembers: { some: { user: { email: { contains: search, mode: 'insensitive' } } } } },
          ],
        }
      }

      const projectStates = await ctx.prisma.projectRoundState.findMany({
        where,
        select: {
          projectId: true,
          state: true,
          project: {
            select: {
              id: true,
              title: true,
              teamName: true,
              competitionCategory: true,
              country: true,
            },
          },
        },
        orderBy: { project: { title: 'asc' } },
      })

      return projectStates.map((ps) => ({
        id: ps.project.id,
        title: ps.project.title,
        teamName: ps.project.teamName,
        competitionCategory: ps.project.competitionCategory,
        country: ps.project.country,
        state: ps.state,
      }))
    }),

  /**
   * Import projects from an earlier round into a later round.
   * Fills intermediate rounds with COMPLETED states to keep history clean.
   */
  importFromRound: adminProcedure
    .input(
      z.object({
        sourceRoundId: z.string(),
        targetRoundId: z.string(),
        projectIds: z.array(z.string()).min(1).max(1000),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { sourceRoundId, targetRoundId, projectIds } = input

      // Validate both rounds exist and belong to the same competition
      const [sourceRound, targetRound] = await Promise.all([
        ctx.prisma.round.findUniqueOrThrow({
          where: { id: sourceRoundId },
          select: { id: true, name: true, competitionId: true, sortOrder: true },
        }),
        ctx.prisma.round.findUniqueOrThrow({
          where: { id: targetRoundId },
          select: { id: true, name: true, competitionId: true, sortOrder: true },
        }),
      ])

      if (sourceRound.competitionId !== targetRound.competitionId) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Source and target rounds must belong to the same competition',
        })
      }

      if (sourceRound.sortOrder >= targetRound.sortOrder) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Source round must come before target round',
        })
      }

      // Validate all projectIds exist in the source round
      const sourceStates = await ctx.prisma.projectRoundState.findMany({
        where: { roundId: sourceRoundId, projectId: { in: projectIds } },
        select: { projectId: true, state: true },
      })

      if (sourceStates.length !== projectIds.length) {
        const foundIds = new Set(sourceStates.map((s) => s.projectId))
        const missing = projectIds.filter((id) => !foundIds.has(id))
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: `${missing.length} project(s) not found in source round`,
        })
      }

      // Find intermediate rounds
      const intermediateRounds = await ctx.prisma.round.findMany({
        where: {
          competitionId: sourceRound.competitionId,
          sortOrder: { gt: sourceRound.sortOrder, lt: targetRound.sortOrder },
        },
        select: { id: true },
        orderBy: { sortOrder: 'asc' },
      })

      // Check which projects are already in the target round
      const existingInTarget = await ctx.prisma.projectRoundState.findMany({
        where: { roundId: targetRoundId, projectId: { in: projectIds } },
        select: { projectId: true },
      })
      const alreadyInTarget = new Set(existingInTarget.map((e) => e.projectId))
      const toImport = projectIds.filter((id) => !alreadyInTarget.has(id))

      if (toImport.length === 0) {
        return { imported: 0, skipped: projectIds.length }
      }

      await ctx.prisma.$transaction(async (tx) => {
        // Update source round states to COMPLETED (if PASSED or PENDING)
        await tx.projectRoundState.updateMany({
          where: {
            roundId: sourceRoundId,
            projectId: { in: toImport },
            state: { in: ['PASSED', 'PENDING', 'IN_PROGRESS'] },
          },
          data: { state: 'COMPLETED' },
        })

        // Create COMPLETED states for intermediate rounds
        if (intermediateRounds.length > 0) {
          const intermediateData = intermediateRounds.flatMap((round) =>
            toImport.map((projectId) => ({
              projectId,
              roundId: round.id,
              state: 'COMPLETED' as const,
            }))
          )
          await tx.projectRoundState.createMany({
            data: intermediateData,
            skipDuplicates: true,
          })
        }

        // Create PENDING states in the target round
        await tx.projectRoundState.createMany({
          data: toImport.map((projectId) => ({
            projectId,
            roundId: targetRoundId,
          })),
          skipDuplicates: true,
        })

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

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

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user?.id,
        action: 'IMPORT_FROM_ROUND',
        entityType: 'Project',
        detailsJson: {
          sourceRoundId,
          sourceRoundName: sourceRound.name,
          targetRoundId,
          targetRoundName: targetRound.name,
          importedCount: toImport.length,
          skippedCount: alreadyInTarget.size,
          intermediateRounds: intermediateRounds.length,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return { imported: toImport.length, skipped: alreadyInTarget.size }
    }),
})
