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

export const liveRouter = router({
  /**
   * Start a live presentation session for a stage
   */
  start: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        projectOrder: z.array(z.string()).min(1), // Ordered project IDs
      })
    )
    .mutation(async ({ ctx, input }) => {
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
      })

      if (round.status !== 'ROUND_ACTIVE') {
        throw new TRPCError({
          code: 'PRECONDITION_FAILED',
          message: 'Round must be ACTIVE to start a live session',
        })
      }

      // Check for existing active cursor
      const existingCursor = await ctx.prisma.liveProgressCursor.findUnique({
        where: { roundId: input.roundId },
      })

      if (existingCursor) {
        throw new TRPCError({
          code: 'CONFLICT',
          message: 'A live session already exists for this round. Use jump/reorder to modify it.',
        })
      }

      // Verify all projects exist
      const projects = await ctx.prisma.project.findMany({
        where: { id: { in: input.projectOrder } },
        select: { id: true },
      })
      if (projects.length !== input.projectOrder.length) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Some project IDs are invalid',
        })
      }

      const cursor = await ctx.prisma.$transaction(async (tx) => {
        // Store the project order in round config
        await tx.round.update({
          where: { id: input.roundId },
          data: {
            configJson: {
              ...(round.configJson as Record<string, unknown> ?? {}),
              projectOrder: input.projectOrder,
            } as Prisma.InputJsonValue,
          },
        })

        const created = await tx.liveProgressCursor.create({
          data: {
            roundId: input.roundId,
            activeProjectId: input.projectOrder[0],
            activeOrderIndex: 0,
            isPaused: false,
          },
        })

        return created
      })

      // Audit outside transaction so failures don't roll back the session start
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'LIVE_SESSION_STARTED',
        entityType: 'Round',
        entityId: input.roundId,
        detailsJson: {
          sessionId: cursor.sessionId,
          projectCount: input.projectOrder.length,
          firstProjectId: input.projectOrder[0],
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return cursor
    }),

  /**
   * Set the active project in the live session
   */
  setActiveProject: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        projectId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const cursor = await ctx.prisma.liveProgressCursor.findUniqueOrThrow({
        where: { roundId: input.roundId },
      })

      // Get project order from round config
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
      })
      const config = (round.configJson as Record<string, unknown>) ?? {}
      const projectOrder = (config.projectOrder as string[]) ?? []

      const index = projectOrder.indexOf(input.projectId)
      if (index === -1) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Project is not in the session order',
        })
      }

      const updated = await ctx.prisma.liveProgressCursor.update({
        where: { id: cursor.id },
        data: {
          activeProjectId: input.projectId,
          activeOrderIndex: index,
        },
      })

      // Audit outside transaction so failures don't roll back the project set
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'LIVE_ACTIVE_PROJECT_SET',
        entityType: 'LiveProgressCursor',
        entityId: cursor.id,
        detailsJson: {
          projectId: input.projectId,
          orderIndex: index,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return updated
    }),

  /**
   * Jump to a specific index in the project order
   */
  jump: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        index: z.number().int().min(0),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const cursor = await ctx.prisma.liveProgressCursor.findUniqueOrThrow({
        where: { roundId: input.roundId },
      })

      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
      })
      const config = (round.configJson as Record<string, unknown>) ?? {}
      const projectOrder = (config.projectOrder as string[]) ?? []

      if (input.index >= projectOrder.length) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: `Index ${input.index} is out of range (0-${projectOrder.length - 1})`,
        })
      }

      const targetProjectId = projectOrder[input.index]

      const updated = await ctx.prisma.liveProgressCursor.update({
        where: { id: cursor.id },
        data: {
          activeProjectId: targetProjectId,
          activeOrderIndex: input.index,
        },
      })

      // Audit outside transaction so failures don't roll back the jump
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'LIVE_JUMP',
        entityType: 'LiveProgressCursor',
        entityId: cursor.id,
        detailsJson: {
          fromIndex: cursor.activeOrderIndex,
          toIndex: input.index,
          projectId: targetProjectId,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return updated
    }),

  /**
   * Reorder the project presentation queue
   */
  reorder: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        projectOrder: z.array(z.string()).min(1),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
      })

      const cursor = await ctx.prisma.liveProgressCursor.findUniqueOrThrow({
        where: { roundId: input.roundId },
      })

      // Update config with new order
      const updated = await ctx.prisma.$transaction(async (tx) => {
        await tx.round.update({
          where: { id: input.roundId },
          data: {
            configJson: {
              ...(round.configJson as Record<string, unknown> ?? {}),
              projectOrder: input.projectOrder,
            } as Prisma.InputJsonValue,
          },
        })

        // Recalculate active index
        const newIndex = cursor.activeProjectId
          ? input.projectOrder.indexOf(cursor.activeProjectId)
          : 0

        const updatedCursor = await tx.liveProgressCursor.update({
          where: { id: cursor.id },
          data: {
            activeOrderIndex: Math.max(0, newIndex),
          },
        })

        return updatedCursor
      })

      // Audit outside transaction so failures don't roll back the reorder
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'LIVE_REORDER',
        entityType: 'LiveProgressCursor',
        entityId: cursor.id,
        detailsJson: {
          projectCount: input.projectOrder.length,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return updated
    }),

  /**
   * Pause the live session
   */
  pause: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const cursor = await ctx.prisma.liveProgressCursor.findUniqueOrThrow({
        where: { roundId: input.roundId },
      })

      if (cursor.isPaused) {
        throw new TRPCError({
          code: 'PRECONDITION_FAILED',
          message: 'Session is already paused',
        })
      }

      const updated = await ctx.prisma.liveProgressCursor.update({
        where: { id: cursor.id },
        data: { isPaused: true },
      })

      // Audit outside transaction so failures don't roll back the pause
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'LIVE_PAUSED',
        entityType: 'LiveProgressCursor',
        entityId: cursor.id,
        detailsJson: { activeProjectId: cursor.activeProjectId },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return updated
    }),

  /**
   * Resume the live session
   */
  resume: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const cursor = await ctx.prisma.liveProgressCursor.findUniqueOrThrow({
        where: { roundId: input.roundId },
      })

      if (!cursor.isPaused) {
        throw new TRPCError({
          code: 'PRECONDITION_FAILED',
          message: 'Session is not paused',
        })
      }

      const updated = await ctx.prisma.liveProgressCursor.update({
        where: { id: cursor.id },
        data: { isPaused: false },
      })

      // Audit outside transaction so failures don't roll back the resume
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'LIVE_RESUMED',
        entityType: 'LiveProgressCursor',
        entityId: cursor.id,
        detailsJson: { activeProjectId: cursor.activeProjectId },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return updated
    }),

  /**
   * Get current cursor state (for all users, including audience)
   */
  getCursor: protectedProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      const cursor = await ctx.prisma.liveProgressCursor.findUnique({
        where: { roundId: input.roundId },
      })

      if (!cursor) {
        return null
      }

      // Get round config for project order
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
      })
      const config = (round.configJson as Record<string, unknown>) ?? {}
      const projectOrder = (config.projectOrder as string[]) ?? []

      // Get current project details
      let activeProject = null
      if (cursor.activeProjectId) {
        activeProject = await ctx.prisma.project.findUnique({
          where: { id: cursor.activeProjectId },
          select: {
            id: true,
            title: true,
            teamName: true,
            description: true,
            tags: true,
          },
        })
      }

      // Get open cohorts for this round (if any)
      const openCohorts = await ctx.prisma.cohort.findMany({
        where: { roundId: input.roundId, isOpen: true },
        select: {
          id: true,
          name: true,
          votingMode: true,
          windowCloseAt: true,
        },
      })

      return {
        ...cursor,
        activeProject,
        projectOrder,
        totalProjects: projectOrder.length,
        openCohorts,
      }
    }),

  /**
   * Cast a vote during a live session (audience or jury)
   * Checks window is open and deduplicates votes
   */
  castVote: audienceProcedure
    .input(
      z.object({
        roundId: z.string(),
        projectId: z.string(),
        score: z.number().int().min(1).max(10),
        criterionScoresJson: z.record(z.number()).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify live session exists and is not paused
      const cursor = await ctx.prisma.liveProgressCursor.findUniqueOrThrow({
        where: { roundId: input.roundId },
      })

      if (cursor.isPaused) {
        throw new TRPCError({
          code: 'PRECONDITION_FAILED',
          message: 'Voting is paused',
        })
      }

      // Check if there's an open cohort containing this project
      const openCohort = await ctx.prisma.cohort.findFirst({
        where: {
          roundId: input.roundId,
          isOpen: true,
          projects: { some: { projectId: input.projectId } },
        },
      })

      // Check voting window if cohort has time limits
      if (openCohort?.windowCloseAt) {
        const now = new Date()
        if (now > openCohort.windowCloseAt) {
          throw new TRPCError({
            code: 'PRECONDITION_FAILED',
            message: 'Voting window has closed',
          })
        }
      }

      // Find the LiveVotingSession linked to this round
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { competition: { select: { programId: true } } },
      })

      // Find or check existing LiveVotingSession for this round
      const session = await ctx.prisma.liveVotingSession.findFirst({
        where: {
          round: { competition: { programId: round.competition.programId } },
          status: 'IN_PROGRESS',
        },
      })

      if (!session) {
        throw new TRPCError({
          code: 'PRECONDITION_FAILED',
          message: 'No active voting session found',
        })
      }

      // Deduplicate: check if user already voted on this project in this session
      const existingVote = await ctx.prisma.liveVote.findUnique({
        where: {
          sessionId_projectId_userId: {
            sessionId: session.id,
            projectId: input.projectId,
            userId: ctx.user.id,
          },
        },
      })

      if (existingVote) {
        // Update existing vote
        const updated = await ctx.prisma.liveVote.update({
          where: { id: existingVote.id },
          data: {
            score: input.score,
            criterionScoresJson: input.criterionScoresJson
              ? (input.criterionScoresJson as Prisma.InputJsonValue)
              : undefined,
            votedAt: new Date(),
          },
        })

        return { vote: updated, wasUpdate: true }
      }

      // Create new vote
      const vote = await ctx.prisma.liveVote.create({
        data: {
          sessionId: session.id,
          projectId: input.projectId,
          userId: ctx.user.id,
          score: input.score,
          isAudienceVote: !['JURY_MEMBER', 'SUPER_ADMIN', 'PROGRAM_ADMIN'].includes(
            ctx.user.role
          ),
          criterionScoresJson: input.criterionScoresJson
            ? (input.criterionScoresJson as Prisma.InputJsonValue)
            : undefined,
        },
      })

      return { vote, wasUpdate: false }
    }),

  // =========================================================================
  // Phase 4: Audience-native procedures
  // =========================================================================

  /**
   * Get audience context for a live session (public-facing via sessionId)
   */
  getAudienceContext: protectedProcedure
    .input(z.object({ sessionId: z.string() }))
    .query(async ({ ctx, input }) => {
      const cursor = await ctx.prisma.liveProgressCursor.findUnique({
        where: { sessionId: input.sessionId },
      })

      if (!cursor) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Live session not found',
        })
      }

      // Get round info
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: cursor.roundId },
        select: {
          id: true,
          name: true,
          status: true,
          configJson: true,
        },
      })

      // Get active project
      let activeProject = null
      if (cursor.activeProjectId) {
        activeProject = await ctx.prisma.project.findUnique({
          where: { id: cursor.activeProjectId },
          select: {
            id: true,
            title: true,
            teamName: true,
            description: true,
            tags: true,
            country: true,
          },
        })
      }

      // Get open cohorts
      const openCohorts = await ctx.prisma.cohort.findMany({
        where: { roundId: cursor.roundId, isOpen: true },
        select: {
          id: true,
          name: true,
          votingMode: true,
          windowOpenAt: true,
          windowCloseAt: true,
          projects: {
            select: { projectId: true },
          },
        },
      })

      const config = (round.configJson as Record<string, unknown>) ?? {}
      const projectOrder = (config.projectOrder as string[]) ?? []

      const now = new Date()
      const isWindowOpen = round.status === 'ROUND_ACTIVE'

      // Aggregate project scores from LiveVote for the scoreboard
      // Find the active LiveVotingSession for this round's program
      const votingSession = await ctx.prisma.liveVotingSession.findFirst({
        where: {
          round: { competition: { programId: round.id } },
          status: 'IN_PROGRESS',
        },
        select: { id: true },
      })

      // Get all cohort project IDs for this stage
      const allCohortProjectIds = openCohorts.flatMap((c) =>
        c.projects.map((p) => p.projectId)
      )
      const uniqueProjectIds = [...new Set(allCohortProjectIds)]

      let projectScores: Array<{
        projectId: string
        title: string
        teamName: string | null
        averageScore: number
        voteCount: number
      }> = []

      if (votingSession && uniqueProjectIds.length > 0) {
        // Get vote aggregates
        const voteAggregates = await ctx.prisma.liveVote.groupBy({
          by: ['projectId'],
          where: {
            sessionId: votingSession.id,
            projectId: { in: uniqueProjectIds },
          },
          _avg: { score: true },
          _count: { score: true },
        })

        // Get project details
        const projects = await ctx.prisma.project.findMany({
          where: { id: { in: uniqueProjectIds } },
          select: { id: true, title: true, teamName: true },
        })

        const projectMap = new Map(projects.map((p) => [p.id, p]))
        projectScores = voteAggregates.map((agg) => {
          const project = projectMap.get(agg.projectId)
          return {
            projectId: agg.projectId,
            title: project?.title ?? 'Unknown',
            teamName: project?.teamName ?? null,
            averageScore: agg._avg.score ?? 0,
            voteCount: agg._count.score,
          }
        })
      }

      return {
        cursor: {
          sessionId: cursor.sessionId,
          activeOrderIndex: cursor.activeOrderIndex,
          isPaused: cursor.isPaused,
          totalProjects: projectOrder.length,
        },
        activeProject,
        openCohorts: openCohorts.map((c) => ({
          id: c.id,
          name: c.name,
          votingMode: c.votingMode,
          windowCloseAt: c.windowCloseAt,
          projectIds: c.projects.map((p) => p.projectId),
        })),
        projectScores,
        roundInfo: {
          id: round.id,
          name: round.name,
        },
        windowStatus: {
          isOpen: true,
          closesAt: null,
        },
      }
    }),

  /**
   * Cast a vote in a stage-native live session
   */
  castStageVote: audienceProcedure
    .input(
      z.object({
        sessionId: z.string(),
        projectId: z.string(),
        score: z.number().int().min(1).max(10),
        dedupeKey: z.string().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Resolve cursor by sessionId
      const cursor = await ctx.prisma.liveProgressCursor.findUnique({
        where: { sessionId: input.sessionId },
      })

      if (!cursor) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Live session not found',
        })
      }

      if (cursor.isPaused) {
        throw new TRPCError({
          code: 'PRECONDITION_FAILED',
          message: 'Voting is paused',
        })
      }

      // Check if there's an open cohort containing this project
      const openCohort = await ctx.prisma.cohort.findFirst({
        where: {
          roundId: cursor.roundId,
          isOpen: true,
          projects: { some: { projectId: input.projectId } },
        },
      })

      if (openCohort?.windowCloseAt) {
        const now = new Date()
        if (now > openCohort.windowCloseAt) {
          throw new TRPCError({
            code: 'PRECONDITION_FAILED',
            message: 'Voting window has closed',
          })
        }
      }

      // Find an active LiveVotingSession
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: cursor.roundId },
        select: { competition: { select: { programId: true } } },
      })

      const session = await ctx.prisma.liveVotingSession.findFirst({
        where: {
          round: { competition: { programId: round.competition.programId } },
          status: 'IN_PROGRESS',
        },
      })

      if (!session) {
        throw new TRPCError({
          code: 'PRECONDITION_FAILED',
          message: 'No active voting session found',
        })
      }

      // Deduplicate: sessionId + projectId + userId
      const existingVote = await ctx.prisma.liveVote.findUnique({
        where: {
          sessionId_projectId_userId: {
            sessionId: session.id,
            projectId: input.projectId,
            userId: ctx.user.id,
          },
        },
      })

      if (existingVote) {
        const updated = await ctx.prisma.liveVote.update({
          where: { id: existingVote.id },
          data: {
            score: input.score,
            votedAt: new Date(),
          },
        })
        return { vote: updated, wasUpdate: true }
      }

      const vote = await ctx.prisma.liveVote.create({
        data: {
          sessionId: session.id,
          projectId: input.projectId,
          userId: ctx.user.id,
          score: input.score,
          isAudienceVote: !['JURY_MEMBER', 'SUPER_ADMIN', 'PROGRAM_ADMIN'].includes(
            ctx.user.role
          ),
        },
      })

      return { vote, wasUpdate: false }
    }),
})
