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

export const competitionRouter = router({
  /**
   * Create a new competition for a program
   */
  create: adminProcedure
    .input(
      z.object({
        programId: z.string(),
        name: z.string().min(1).max(255),
        slug: z.string().min(1).max(100).regex(/^[a-z0-9-]+$/),
        categoryMode: z.string().default('SHARED'),
        startupFinalistCount: z.number().int().positive().default(3),
        conceptFinalistCount: z.number().int().positive().default(3),
        notifyOnRoundAdvance: z.boolean().default(true),
        notifyOnDeadlineApproach: z.boolean().default(true),
        deadlineReminderDays: z.array(z.number().int().positive()).default([7, 3, 1]),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const existing = await ctx.prisma.competition.findUnique({
        where: { slug: input.slug },
      })
      if (existing) {
        throw new TRPCError({
          code: 'CONFLICT',
          message: `A competition with slug "${input.slug}" already exists`,
        })
      }

      await ctx.prisma.program.findUniqueOrThrow({
        where: { id: input.programId },
      })

      const competition = await ctx.prisma.competition.create({
        data: input,
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'Competition',
        entityId: competition.id,
        detailsJson: { name: input.name, programId: input.programId },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return competition
    }),

  /**
   * Resolve the id of the most-recent ACTIVE program for the logged-in user.
   * Used by client pages (e.g. the finalist-documents judge review) that need a
   * programId but don't have one in the route. Returns null when none is active.
   */
  getActiveProgramId: protectedProcedure.query(async ({ ctx }) => {
    const program = await ctx.prisma.program.findFirst({
      where: { status: 'ACTIVE' },
      orderBy: { year: 'desc' },
      select: { id: true },
    })
    return program?.id ?? null
  }),

  /**
   * Get competition by ID with rounds, jury groups, and submission windows
   */
  getById: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const competition = await ctx.prisma.competition.findUnique({
        where: { id: input.id },
        include: {
          rounds: {
            orderBy: { sortOrder: 'asc' },
            select: {
              id: true,
              name: true,
              slug: true,
              roundType: true,
              status: true,
              sortOrder: true,
              windowOpenAt: true,
              windowCloseAt: true,
              specialAwardId: true,
              juryGroup: {
                select: { id: true, name: true },
              },
              _count: {
                select: {
                  projectRoundStates: true,
                  assignments: true,
                },
              },
            },
          },
          juryGroups: {
            orderBy: { sortOrder: 'asc' },
            select: {
              id: true,
              name: true,
              slug: true,
              sortOrder: true,
              defaultMaxAssignments: true,
              defaultCapMode: true,
              _count: { select: { members: true } },
            },
          },
          submissionWindows: {
            orderBy: { sortOrder: 'asc' },
            select: {
              id: true,
              name: true,
              slug: true,
              roundNumber: true,
              windowOpenAt: true,
              windowCloseAt: true,
              isLocked: true,
              deadlinePolicy: true,
              graceHours: true,
              lockOnClose: true,
              sortOrder: true,
              _count: { select: { fileRequirements: true, projectFiles: true } },
            },
          },
        },
      })

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

      // Count distinct projects across all rounds (not sum of per-round states)
      const roundIds = competition.rounds.filter((r) => !r.specialAwardId).map((r) => r.id)
      const distinctProjectCount = roundIds.length > 0
        ? await ctx.prisma.projectRoundState.findMany({
            where: { roundId: { in: roundIds } },
            select: { projectId: true },
            distinct: ['projectId'],
          }).then((rows) => rows.length)
        : 0

      return { ...competition, distinctProjectCount }
    }),

  /**
   * List competitions for a program
   */
  list: adminProcedure
    .input(z.object({ programId: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.competition.findMany({
        where: { programId: input.programId },
        orderBy: { createdAt: 'desc' },
        include: {
          _count: {
            select: {
              rounds: { where: { specialAwardId: null } },
              juryGroups: true,
              submissionWindows: true,
            },
          },
        },
      })
    }),

  /**
   * Update competition settings
   */
  update: adminProcedure
    .input(
      z.object({
        id: z.string(),
        name: z.string().min(1).max(255).optional(),
        slug: z.string().min(1).max(100).regex(/^[a-z0-9-]+$/).optional(),
        status: z.enum(['DRAFT', 'ACTIVE', 'CLOSED', 'ARCHIVED']).optional(),
        categoryMode: z.string().optional(),
        startupFinalistCount: z.number().int().positive().optional(),
        conceptFinalistCount: z.number().int().positive().optional(),
        notifyOnRoundAdvance: z.boolean().optional(),
        notifyOnDeadlineApproach: z.boolean().optional(),
        deadlineReminderDays: z.array(z.number().int().positive()).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id, ...data } = input

      if (data.slug) {
        const existing = await ctx.prisma.competition.findFirst({
          where: { slug: data.slug, NOT: { id } },
        })
        if (existing) {
          throw new TRPCError({
            code: 'CONFLICT',
            message: `A competition with slug "${data.slug}" already exists`,
          })
        }
      }

      const previous = await ctx.prisma.competition.findUniqueOrThrow({ where: { id } })

      const competition = await ctx.prisma.competition.update({
        where: { id },
        data,
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'Competition',
        entityId: id,
        detailsJson: {
          changes: data,
          previous: {
            name: previous.name,
            status: previous.status,
            slug: previous.slug,
          },
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return competition
    }),

  /**
   * Delete (archive) a competition
   */
  delete: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const competition = await ctx.prisma.competition.update({
        where: { id: input.id },
        data: { status: 'ARCHIVED' },
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'DELETE',
        entityType: 'Competition',
        entityId: input.id,
        detailsJson: { action: 'archived' },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return competition
    }),

  /**
   * Get competitions where the current user is a jury group member
   */
  getMyCompetitions: protectedProcedure.query(async ({ ctx }) => {
    // Find competitions where the user is a jury group member
    const memberships = await ctx.prisma.juryGroupMember.findMany({
      where: { userId: ctx.user.id },
      select: { juryGroup: { select: { competitionId: true } } },
    })
    const competitionIds = [...new Set(memberships.map((m) => m.juryGroup.competitionId))]
    if (competitionIds.length === 0) return []
    return ctx.prisma.competition.findMany({
      where: { id: { in: competitionIds }, status: { not: 'ARCHIVED' } },
      include: {
        rounds: {
          orderBy: { sortOrder: 'asc' },
          select: { id: true, name: true, roundType: true, status: true },
        },
        _count: { select: { rounds: { where: { specialAwardId: null } }, juryGroups: true } },
      },
      orderBy: { createdAt: 'desc' },
    })
  }),
})
