import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, adminProcedure } from '../trpc'
import { logAudit } from '../utils/audit'
import { sendAnnouncementEmail } from '@/lib/email'
import type { PrismaClient } from '@prisma/client'

/**
 * Send round-entry notification emails to project team members.
 * Fire-and-forget: errors are logged but never block the assignment.
 */
async function sendRoundEntryEmails(
  prisma: PrismaClient,
  projectIds: string[],
  roundName: string,
) {
  try {
    // Fetch projects with team members' user emails + fallback submittedByEmail
    const projects = await prisma.project.findMany({
      where: { id: { in: projectIds } },
      select: {
        id: true,
        title: true,
        submittedByEmail: true,
        teamMembers: {
          select: {
            user: { select: { email: true, name: true } },
          },
        },
      },
    })

    const emailPromises: Promise<void>[] = []

    for (const project of projects) {
      // Collect unique emails for this project
      const recipients = new Map<string, string | null>()

      for (const tm of project.teamMembers) {
        if (tm.user.email) {
          recipients.set(tm.user.email, tm.user.name)
        }
      }

      // Fallback: if no team members have emails, use submittedByEmail
      if (recipients.size === 0 && project.submittedByEmail) {
        recipients.set(project.submittedByEmail, null)
      }

      for (const [email, name] of recipients) {
        emailPromises.push(
          sendAnnouncementEmail(
            email,
            name,
            `Your project has entered: ${roundName}`,
            `Your project "${project.title}" has been added to the round "${roundName}" in the Monaco Ocean Protection Challenge. You will receive further instructions as the round progresses.`,
            'View Your Dashboard',
            `${process.env.NEXTAUTH_URL || 'https://monaco-opc.com'}/dashboard`,
          ).catch((err) => {
            console.error(`[round-entry-email] Failed to send to ${email}:`, err)
          }),
        )
      }
    }

    await Promise.allSettled(emailPromises)
  } catch (err) {
    console.error('[round-entry-email] Failed to send round entry emails:', err)
  }
}

/**
 * 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
      if (search) {
        where.OR = [
          { title: { contains: search, mode: 'insensitive' } },
          { teamName: { contains: search, mode: 'insensitive' } },
          { description: { 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(200),
        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 and get config
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: roundId },
        select: { id: true, name: true, configJson: 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,
      })

      // Send round-entry notification emails if enabled (fire-and-forget)
      const config = (round.configJson as Record<string, unknown>) || {}
      if (config.notifyOnEntry) {
        void sendRoundEntryEmails(ctx.prisma as unknown as PrismaClient, projectIds, round.name)
      }

      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 and get config
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: roundId },
        select: { id: true, name: true, configJson: 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,
      })

      // Send round-entry notification emails if enabled (fire-and-forget)
      const config = (round.configJson as Record<string, unknown>) || {}
      if (config.notifyOnEntry) {
        void sendRoundEntryEmails(ctx.prisma as unknown as PrismaClient, projectIds, round.name)
      }

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