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

export const cohortRouter = router({
  /**
   * Create a new cohort within a round
   */
  create: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        name: z.string().min(1).max(255),
        votingMode: z.enum(['simple', 'criteria', 'ranked']).default('simple'),
        windowOpenAt: z.date().optional(),
        windowCloseAt: z.date().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify round exists
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
      })

      // Validate window dates
      if (input.windowOpenAt && input.windowCloseAt) {
        if (input.windowCloseAt <= input.windowOpenAt) {
          throw new TRPCError({
            code: 'BAD_REQUEST',
            message: 'Window close date must be after open date',
          })
        }
      }

      const cohort = await ctx.prisma.cohort.create({
        data: {
          roundId: input.roundId,
          name: input.name,
          votingMode: input.votingMode,
          windowOpenAt: input.windowOpenAt ?? null,
          windowCloseAt: input.windowCloseAt ?? null,
        },
      })

      // Audit outside transaction so failures don't roll back the create
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'Cohort',
        entityId: cohort.id,
        detailsJson: {
          roundId: input.roundId,
          name: input.name,
          votingMode: input.votingMode,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return cohort
    }),

  /**
   * Assign projects to a cohort
   */
  assignProjects: adminProcedure
    .input(
      z.object({
        cohortId: z.string(),
        projectIds: z.array(z.string()).min(1).max(200),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify cohort exists
      const cohort = await ctx.prisma.cohort.findUniqueOrThrow({
        where: { id: input.cohortId },
      })

      if (cohort.isOpen) {
        throw new TRPCError({
          code: 'PRECONDITION_FAILED',
          message: 'Cannot modify projects while voting is open',
        })
      }

      // Get current max sortOrder
      const maxOrder = await ctx.prisma.cohortProject.aggregate({
        where: { cohortId: input.cohortId },
        _max: { sortOrder: true },
      })
      let nextOrder = (maxOrder._max.sortOrder ?? -1) + 1

      // Create cohort project entries (skip duplicates)
      const created = await ctx.prisma.cohortProject.createMany({
        data: input.projectIds.map((projectId) => ({
          cohortId: input.cohortId,
          projectId,
          sortOrder: nextOrder++,
        })),
        skipDuplicates: true,
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'COHORT_PROJECTS_ASSIGNED',
        entityType: 'Cohort',
        entityId: input.cohortId,
        detailsJson: {
          projectCount: created.count,
          requested: input.projectIds.length,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return { assigned: created.count, requested: input.projectIds.length }
    }),

  /**
   * Open voting for a cohort
   */
  openVoting: adminProcedure
    .input(
      z.object({
        cohortId: z.string(),
        durationMinutes: z.number().int().min(1).max(1440).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const cohort = await ctx.prisma.cohort.findUniqueOrThrow({
        where: { id: input.cohortId },
        include: { _count: { select: { projects: true } } },
      })

      if (cohort.isOpen) {
        throw new TRPCError({
          code: 'CONFLICT',
          message: 'Voting is already open for this cohort',
        })
      }

      if (cohort._count.projects === 0) {
        throw new TRPCError({
          code: 'PRECONDITION_FAILED',
          message: 'Cohort must have at least one project before opening voting',
        })
      }

      const now = new Date()
      const closeAt = input.durationMinutes
        ? new Date(now.getTime() + input.durationMinutes * 60 * 1000)
        : cohort.windowCloseAt

      const updated = await ctx.prisma.cohort.update({
        where: { id: input.cohortId },
        data: {
          isOpen: true,
          windowOpenAt: now,
          windowCloseAt: closeAt,
        },
      })

      // Audit outside transaction so failures don't roll back the voting open
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'COHORT_VOTING_OPENED',
        entityType: 'Cohort',
        entityId: input.cohortId,
        detailsJson: {
          openedAt: now.toISOString(),
          closesAt: closeAt?.toISOString() ?? null,
          projectCount: cohort._count.projects,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return updated
    }),

  /**
   * Close voting for a cohort
   */
  closeVoting: adminProcedure
    .input(z.object({ cohortId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const cohort = await ctx.prisma.cohort.findUniqueOrThrow({
        where: { id: input.cohortId },
      })

      if (!cohort.isOpen) {
        throw new TRPCError({
          code: 'PRECONDITION_FAILED',
          message: 'Voting is not currently open for this cohort',
        })
      }

      const now = new Date()

      const updated = await ctx.prisma.cohort.update({
        where: { id: input.cohortId },
        data: {
          isOpen: false,
          windowCloseAt: now,
        },
      })

      // Audit outside transaction so failures don't roll back the voting close
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'COHORT_VOTING_CLOSED',
        entityType: 'Cohort',
        entityId: input.cohortId,
        detailsJson: {
          closedAt: now.toISOString(),
          wasOpenSince: cohort.windowOpenAt?.toISOString(),
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return updated
    }),

  /**
   * List cohorts for a round
   */
  list: protectedProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.cohort.findMany({
        where: { roundId: input.roundId },
        orderBy: { createdAt: 'asc' },
        include: {
          _count: { select: { projects: true } },
        },
      })
    }),

  /**
   * Get cohort with projects and vote summary
   */
  get: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const cohort = await ctx.prisma.cohort.findUniqueOrThrow({
        where: { id: input.id },
        include: {
          round: {
            select: {
              id: true,
              name: true,
              competition: { select: { id: true, name: true } },
            },
          },
          projects: {
            orderBy: { sortOrder: 'asc' },
            include: {
              project: {
                select: {
                  id: true,
                  title: true,
                  teamName: true,
                  tags: true,
                  description: true,
                },
              },
            },
          },
        },
      })

      // Get vote counts per project in the cohort's round session
      const projectIds = cohort.projects.map((p) => p.projectId)
      const voteSummary =
        projectIds.length > 0
          ? await ctx.prisma.liveVote.groupBy({
              by: ['projectId'],
              where: {
                projectId: { in: projectIds },
                session: { roundId: cohort.round.id },
              },
              _count: true,
              _avg: { score: true },
            })
          : []

      const voteMap = new Map(
        voteSummary.map((v) => [
          v.projectId,
          { voteCount: v._count, avgScore: v._avg?.score ?? 0 },
        ])
      )

      return {
        ...cohort,
        projects: cohort.projects.map((cp) => ({
          ...cp,
          votes: voteMap.get(cp.projectId) ?? { voteCount: 0, avgScore: 0 },
        })),
      }
    }),
})
