import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { randomUUID } from 'crypto'
import { router, protectedProcedure, adminProcedure, publicProcedure } from '../trpc'
import { logAudit } from '../utils/audit'
interface LiveVotingCriterion {
  id: string
  label: string
  description?: string
  scale: number
  weight: number
}

export const liveVotingRouter = router({
  /**
   * Get or create a live voting session for a round
   */
  getSession: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      let session = await ctx.prisma.liveVotingSession.findUnique({
        where: { roundId: input.roundId },
        include: {
          round: {
            include: {
              competition: {
                include: {
                  program: { select: { name: true, year: true } },
                },
              },
            },
          },
        },
      })

      if (!session) {
        session = await ctx.prisma.liveVotingSession.create({
          data: {
            roundId: input.roundId,
          },
          include: {
            round: {
              include: {
                competition: {
                  include: {
                    program: { select: { name: true, year: true } },
                  },
                },
              },
            },
          },
        })
      }

      // Get current votes if voting is in progress
      let currentVotes: { userId: string | null; score: number }[] = []
      if (session.currentProjectId) {
        const votes = await ctx.prisma.liveVote.findMany({
          where: {
            sessionId: session.id,
            projectId: session.currentProjectId,
          },
          select: { userId: true, score: true },
        })
        currentVotes = votes
      }

      // Get audience voter count
      const audienceVoterCount = await ctx.prisma.audienceVoter.count({
        where: { sessionId: session.id },
      })

      return {
        ...session,
        currentVotes,
        audienceVoterCount,
      }
    }),

  /**
   * Get session for jury member voting
   */
  getSessionForVoting: protectedProcedure
    .input(z.object({ sessionId: z.string() }))
    .query(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.findUniqueOrThrow({
        where: { id: input.sessionId },
        include: {
          round: {
            include: {
              competition: {
                include: {
                  program: { select: { name: true, year: true } },
                },
              },
            },
          },
        },
      })

      let currentProject = null
      if (session.currentProjectId && session.status === 'IN_PROGRESS') {
        currentProject = await ctx.prisma.project.findUnique({
          where: { id: session.currentProjectId },
          select: { id: true, title: true, teamName: true, description: true },
        })
      }

      let userVote = null
      if (session.currentProjectId) {
        userVote = await ctx.prisma.liveVote.findFirst({
          where: {
            sessionId: session.id,
            projectId: session.currentProjectId,
            userId: ctx.user.id,
          },
        })
      }

      let timeRemaining = null
      if (session.votingEndsAt && session.status === 'IN_PROGRESS') {
        const remaining = new Date(session.votingEndsAt).getTime() - Date.now()
        timeRemaining = Math.max(0, Math.floor(remaining / 1000))
      }

      return {
        session: {
          id: session.id,
          status: session.status,
          votingStartedAt: session.votingStartedAt,
          votingEndsAt: session.votingEndsAt,
          votingMode: session.votingMode,
          criteriaJson: session.criteriaJson,
        },
        round: session.round,
        currentProject,
        userVote,
        timeRemaining,
      }
    }),

  /**
   * Get public session info for display
   */
  getPublicSession: protectedProcedure
    .input(z.object({ sessionId: z.string() }))
    .query(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.findUniqueOrThrow({
        where: { id: input.sessionId },
        include: {
          round: {
            include: {
              competition: {
                include: {
                  program: { select: { name: true, year: true } },
                },
              },
            },
          },
        },
      })

      const projectOrder = (session.projectOrderJson as string[]) || []
      const projects = await ctx.prisma.project.findMany({
        where: { id: { in: projectOrder } },
        select: { id: true, title: true, teamName: true },
      })

      const sortedProjects = projectOrder
        .map((id) => projects.find((p) => p.id === id))
        .filter(Boolean)

      const scores = await ctx.prisma.liveVote.groupBy({
        by: ['projectId'],
        where: { sessionId: session.id },
        _avg: { score: true },
        _count: true,
      })

      const projectsWithScores = sortedProjects.map((project) => {
        const projectScore = scores.find((s) => s.projectId === project!.id)
        return {
          ...project,
          averageScore: projectScore?._avg.score || null,
          voteCount: projectScore?._count || 0,
        }
      })

      return {
        session: {
          id: session.id,
          status: session.status,
          currentProjectId: session.currentProjectId,
          votingEndsAt: session.votingEndsAt,
        },
        round: session.round,
        projects: projectsWithScores,
      }
    }),

  /**
   * Set project order for voting
   */
  setProjectOrder: adminProcedure
    .input(
      z.object({
        sessionId: z.string(),
        projectIds: z.array(z.string()),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.update({
        where: { id: input.sessionId },
        data: {
          projectOrderJson: input.projectIds,
        },
      })

      return session
    }),

  /**
   * Set voting mode (simple vs criteria)
   */
  setVotingMode: adminProcedure
    .input(
      z.object({
        sessionId: z.string(),
        votingMode: z.enum(['simple', 'criteria']),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.update({
        where: { id: input.sessionId },
        data: { votingMode: input.votingMode },
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'SET_VOTING_MODE',
        entityType: 'LiveVotingSession',
        entityId: session.id,
        detailsJson: { votingMode: input.votingMode },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return session
    }),

  /**
   * Set criteria for criteria-based voting
   */
  setCriteria: adminProcedure
    .input(
      z.object({
        sessionId: z.string(),
        criteria: z.array(
          z.object({
            id: z.string(),
            label: z.string(),
            description: z.string().optional(),
            scale: z.number().int().min(1).max(100),
            weight: z.number().min(0).max(1),
          })
        ),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Validate weights sum approximately to 1
      const weightSum = input.criteria.reduce((sum, c) => sum + c.weight, 0)
      if (Math.abs(weightSum - 1) > 0.01) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: `Criteria weights must sum to 1.0 (currently ${weightSum.toFixed(2)})`,
        })
      }

      const session = await ctx.prisma.liveVotingSession.update({
        where: { id: input.sessionId },
        data: {
          criteriaJson: input.criteria,
          votingMode: 'criteria',
        },
      })

      return session
    }),

  /**
   * Import criteria from an existing evaluation form
   */
  importCriteriaFromForm: adminProcedure
    .input(
      z.object({
        sessionId: z.string(),
        formId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const form = await ctx.prisma.evaluationForm.findUniqueOrThrow({
        where: { id: input.formId },
      })

      const formCriteria = form.criteriaJson as Array<{
        id: string
        label: string
        description?: string
        scale: number
        weight: number
        type?: string
      }>

      // Filter out section headers and convert
      const scoringCriteria = formCriteria.filter(
        (c) => !c.type || c.type === 'numeric'
      )

      if (scoringCriteria.length === 0) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'No numeric criteria found in this evaluation form',
        })
      }

      // Normalize weights to sum to 1
      const totalWeight = scoringCriteria.reduce((sum, c) => sum + (c.weight || 1), 0)
      const criteria: LiveVotingCriterion[] = scoringCriteria.map((c) => ({
        id: c.id,
        label: c.label,
        description: c.description,
        scale: c.scale || 10,
        weight: (c.weight || 1) / totalWeight,
      }))

      const session = await ctx.prisma.liveVotingSession.update({
        where: { id: input.sessionId },
        data: {
          criteriaJson: criteria as unknown as import('@prisma/client').Prisma.InputJsonValue,
          votingMode: 'criteria',
        },
      })

      return session
    }),

  /**
   * Start voting for a project
   */
  startVoting: adminProcedure
    .input(
      z.object({
        sessionId: z.string(),
        projectId: z.string(),
        durationSeconds: z.number().int().min(10).max(300).default(30),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const now = new Date()
      const votingEndsAt = new Date(now.getTime() + input.durationSeconds * 1000)

      const session = await ctx.prisma.liveVotingSession.update({
        where: { id: input.sessionId },
        data: {
          status: 'IN_PROGRESS',
          currentProjectId: input.projectId,
          votingStartedAt: now,
          votingEndsAt,
        },
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'START_VOTING',
        entityType: 'LiveVotingSession',
        entityId: session.id,
        detailsJson: { projectId: input.projectId, durationSeconds: input.durationSeconds },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return session
    }),

  /**
   * Stop voting
   */
  stopVoting: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.update({
        where: { id: input.sessionId },
        data: {
          status: 'PAUSED',
          votingEndsAt: new Date(),
        },
      })

      return session
    }),

  /**
   * End session
   */
  endSession: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.update({
        where: { id: input.sessionId },
        data: {
          status: 'COMPLETED',
        },
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'END_SESSION',
        entityType: 'LiveVotingSession',
        entityId: session.id,
        detailsJson: {},
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return session
    }),

  /**
   * Submit a vote (supports both simple and criteria modes)
   *
   * SECURITY: This endpoint records JURY votes only (isAudienceVote=false).
   * Audience voters use `castAudienceVote` (publicProcedure with separate
   * AudienceVoter records). Caller must be either an admin or a member of
   * the round's jury group; otherwise FORBIDDEN.
   */
  vote: protectedProcedure
    .input(
      z.object({
        sessionId: z.string(),
        projectId: z.string(),
        score: z.number().int().min(1).max(10),
        criterionScores: z
          .record(z.string(), z.number())
          .optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify session is in progress
      const session = await ctx.prisma.liveVotingSession.findUniqueOrThrow({
        where: { id: input.sessionId },
        include: {
          round: {
            select: { id: true, juryGroupId: true },
          },
        },
      })

      // Authorization: admins always allowed; otherwise the caller must be a
      // JuryGroupMember of the round's jury group. JURY_MEMBER role alone is
      // not enough — we verify the actual membership row.
      const isAdmin =
        ctx.user.role === 'SUPER_ADMIN' || ctx.user.role === 'PROGRAM_ADMIN'
      if (!isAdmin) {
        const juryGroupId = session.round?.juryGroupId
        if (!juryGroupId) {
          throw new TRPCError({
            code: 'FORBIDDEN',
            message: 'No jury group is associated with this voting session',
          })
        }
        const membership = await ctx.prisma.juryGroupMember.findUnique({
          where: {
            juryGroupId_userId: {
              juryGroupId,
              userId: ctx.user.id,
            },
          },
        })
        if (!membership) {
          throw new TRPCError({
            code: 'FORBIDDEN',
            message: 'You are not a member of this jury group',
          })
        }
      }

      if (session.status !== 'IN_PROGRESS') {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Voting is not currently active',
        })
      }

      if (session.currentProjectId !== input.projectId) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Cannot vote for this project right now',
        })
      }

      // Check if voting window is still open
      if (session.votingEndsAt && new Date() > session.votingEndsAt) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Voting window has closed',
        })
      }

      // For criteria mode, validate and compute weighted score
      let finalScore = input.score
      let criterionScoresJson = null

      if (session.votingMode === 'criteria' && input.criterionScores) {
        const criteria = session.criteriaJson as LiveVotingCriterion[] | null
        if (!criteria || criteria.length === 0) {
          throw new TRPCError({
            code: 'BAD_REQUEST',
            message: 'No criteria configured for this session',
          })
        }

        // Validate all required criteria have scores
        for (const c of criteria) {
          if (input.criterionScores[c.id] === undefined) {
            throw new TRPCError({
              code: 'BAD_REQUEST',
              message: `Missing score for criterion: ${c.label}`,
            })
          }
          const cScore = input.criterionScores[c.id]
          if (cScore < 1 || cScore > c.scale) {
            throw new TRPCError({
              code: 'BAD_REQUEST',
              message: `Score for ${c.label} must be between 1 and ${c.scale}`,
            })
          }
        }

        // Compute weighted score normalized to 1-10
        let weightedSum = 0
        for (const c of criteria) {
          const normalizedScore = (input.criterionScores[c.id] / c.scale) * 10
          weightedSum += normalizedScore * c.weight
        }
        finalScore = Math.round(Math.min(10, Math.max(1, weightedSum)))
        criterionScoresJson = input.criterionScores
      }

      // Upsert vote (allow vote change during window). Explicitly recorded
      // as a jury vote — audience votes go through castAudienceVote.
      const vote = await ctx.prisma.liveVote.upsert({
        where: {
          sessionId_projectId_userId: {
            sessionId: input.sessionId,
            projectId: input.projectId,
            userId: ctx.user.id,
          },
        },
        create: {
          sessionId: input.sessionId,
          projectId: input.projectId,
          userId: ctx.user.id,
          score: finalScore,
          isAudienceVote: false,
          criterionScoresJson: criterionScoresJson ?? undefined,
        },
        update: {
          score: finalScore,
          isAudienceVote: false,
          criterionScoresJson: criterionScoresJson ?? undefined,
          votedAt: new Date(),
        },
      })

      return vote
    }),

  /**
   * Get results for a session (with weighted jury + audience scoring)
   */
  getResults: protectedProcedure
    .input(
      z.object({
        sessionId: z.string(),
        juryWeight: z.number().min(0).max(1).optional(),
        audienceWeight: z.number().min(0).max(1).optional(),
      })
    )
    .query(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.findUniqueOrThrow({
        where: { id: input.sessionId },
        include: {
          round: {
            include: {
              competition: {
                include: {
                  program: { select: { name: true, year: true } },
                },
              },
            },
          },
        },
      })

      // Use custom weights if provided, else session defaults
      const audienceWeightVal = input.audienceWeight ?? session.audienceVoteWeight ?? 0
      const juryWeightVal = input.juryWeight ?? (1 - audienceWeightVal)

      // Get jury votes grouped by project
      const juryScores = await ctx.prisma.liveVote.groupBy({
        by: ['projectId'],
        where: { sessionId: input.sessionId, isAudienceVote: false },
        _avg: { score: true },
        _count: true,
      })

      // Get audience votes grouped by project
      const audienceScores = await ctx.prisma.liveVote.groupBy({
        by: ['projectId'],
        where: { sessionId: input.sessionId, isAudienceVote: true },
        _avg: { score: true },
        _count: true,
      })

      // Get project details
      const allProjectIds = [
        ...new Set([
          ...juryScores.map((s) => s.projectId),
          ...audienceScores.map((s) => s.projectId),
        ]),
      ]
      const projects = await ctx.prisma.project.findMany({
        where: { id: { in: allProjectIds } },
        select: { id: true, title: true, teamName: true },
      })

      const audienceMap = new Map(audienceScores.map((s) => [s.projectId, s]))

      // For criteria mode, get per-criterion breakdowns
      let criteriaBreakdown: Record<string, Record<string, number>> | null = null
      if (session.votingMode === 'criteria') {
        const allJuryVotes = await ctx.prisma.liveVote.findMany({
          where: { sessionId: input.sessionId, isAudienceVote: false },
          select: { projectId: true, criterionScoresJson: true },
        })

        criteriaBreakdown = {}
        for (const vote of allJuryVotes) {
          if (!vote.criterionScoresJson) continue
          const scores = vote.criterionScoresJson as Record<string, number>
          if (!criteriaBreakdown[vote.projectId]) {
            criteriaBreakdown[vote.projectId] = {}
          }
          for (const [criterionId, score] of Object.entries(scores)) {
            if (!criteriaBreakdown[vote.projectId][criterionId]) {
              criteriaBreakdown[vote.projectId][criterionId] = 0
            }
            criteriaBreakdown[vote.projectId][criterionId] += score
          }
        }
        // Average the scores
        for (const projectId of Object.keys(criteriaBreakdown)) {
          const projectVoteCount = allJuryVotes.filter((v) => v.projectId === projectId).length
          if (projectVoteCount > 0) {
            for (const criterionId of Object.keys(criteriaBreakdown[projectId])) {
              criteriaBreakdown[projectId][criterionId] /= projectVoteCount
            }
          }
        }
      }

      // Combine and calculate weighted scores
      const results = juryScores
        .map((jurySc) => {
          const project = projects.find((p) => p.id === jurySc.projectId)
          const audienceSc = audienceMap.get(jurySc.projectId)
          const juryAvg = jurySc._avg?.score || 0
          const audienceAvg = audienceSc?._avg?.score || 0
          const weightedTotal = audienceWeightVal > 0 && audienceSc
            ? juryAvg * juryWeightVal + audienceAvg * audienceWeightVal
            : juryAvg

          return {
            project,
            juryAverage: juryAvg,
            juryVoteCount: jurySc._count,
            audienceAverage: audienceAvg,
            audienceVoteCount: audienceSc?._count || 0,
            weightedTotal,
            criteriaAverages: criteriaBreakdown?.[jurySc.projectId] || null,
          }
        })
        .sort((a, b) => b.weightedTotal - a.weightedTotal)

      // Detect ties
      const ties: string[][] = []
      for (let i = 0; i < results.length - 1; i++) {
        if (Math.abs(results[i].weightedTotal - results[i + 1].weightedTotal) < 0.001) {
          const tieGroup = [results[i].project?.id, results[i + 1].project?.id].filter(Boolean) as string[]
          ties.push(tieGroup)
        }
      }

      return {
        session,
        results,
        ties,
        tieBreakerMethod: session.tieBreakerMethod,
        votingMode: session.votingMode,
        criteria: session.criteriaJson as LiveVotingCriterion[] | null,
        weights: { jury: juryWeightVal, audience: audienceWeightVal },
      }
    }),

  /**
   * Update presentation settings for a live voting session
   */
  updatePresentationSettings: adminProcedure
    .input(
      z.object({
        sessionId: z.string(),
        presentationSettingsJson: z.object({
          theme: z.string().optional(),
          autoAdvance: z.boolean().optional(),
          autoAdvanceDelay: z.number().int().min(5).max(120).optional(),
          scoreDisplayFormat: z.enum(['bar', 'number', 'radial']).optional(),
          showVoteCount: z.boolean().optional(),
          brandingOverlay: z.string().optional(),
        }),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.update({
        where: { id: input.sessionId },
        data: {
          presentationSettingsJson: input.presentationSettingsJson,
        },
      })

      return session
    }),

  /**
   * Update session config (audience voting, tie-breaker)
   */
  updateSessionConfig: adminProcedure
    .input(
      z.object({
        sessionId: z.string(),
        allowAudienceVotes: z.boolean().optional(),
        audienceVoteWeight: z.number().min(0).max(1).optional(),
        tieBreakerMethod: z.enum(['admin_decides', 'highest_individual', 'revote']).optional(),
        audienceVotingMode: z.enum(['disabled', 'per_project', 'per_category', 'favorites']).optional(),
        audienceMaxFavorites: z.number().int().min(1).max(20).optional(),
        audienceRequireId: z.boolean().optional(),
        audienceVotingDuration: z.number().int().min(1).max(600).nullable().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { sessionId, ...data } = input

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

      // Audit log
      try {
        await logAudit({
          prisma: ctx.prisma,
          userId: ctx.user.id,
          action: 'UPDATE_SESSION_CONFIG',
          entityType: 'LiveVotingSession',
          entityId: sessionId,
          detailsJson: data,
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        })
      } catch (err) {
        console.error('[LiveVoting] Audit log failed:', err)
      }

      return session
    }),

  /**
   * Register an audience voter (public, no auth required)
   */
  registerAudienceVoter: publicProcedure
    .input(
      z.object({
        sessionId: z.string(),
        identifier: z.string().optional(),
        identifierType: z.enum(['email', 'phone', 'name', 'anonymous']).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.findUniqueOrThrow({
        where: { id: input.sessionId },
      })

      if (!session.allowAudienceVotes) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'Audience voting is not enabled for this session',
        })
      }

      if (session.audienceRequireId && !input.identifier) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Identification is required for audience voting',
        })
      }

      const token = randomUUID()

      const voter = await ctx.prisma.audienceVoter.create({
        data: {
          sessionId: input.sessionId,
          token,
          identifier: input.identifier || null,
          identifierType: input.identifierType || 'anonymous',
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        },
      })

      return { token: voter.token, voterId: voter.id }
    }),

  /**
   * Cast an audience vote (token-based, no auth required)
   */
  castAudienceVote: publicProcedure
    .input(
      z.object({
        sessionId: z.string(),
        projectId: z.string(),
        score: z.number().int().min(1).max(10),
        token: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify voter token
      const voter = await ctx.prisma.audienceVoter.findUnique({
        where: { token: input.token },
      })

      if (!voter || voter.sessionId !== input.sessionId) {
        throw new TRPCError({
          code: 'UNAUTHORIZED',
          message: 'Invalid voting token',
        })
      }

      // Verify session is in progress and allows audience votes
      const session = await ctx.prisma.liveVotingSession.findUniqueOrThrow({
        where: { id: input.sessionId },
      })

      if (session.status !== 'IN_PROGRESS') {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Voting is not currently active',
        })
      }

      if (!session.allowAudienceVotes) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'Audience voting is not enabled for this session',
        })
      }

      if (session.currentProjectId !== input.projectId) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Cannot vote for this project right now',
        })
      }

      if (session.votingEndsAt && new Date() > session.votingEndsAt) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Voting window has closed',
        })
      }

      // Upsert audience vote (dedup by audienceVoterId)
      const vote = await ctx.prisma.liveVote.upsert({
        where: {
          sessionId_projectId_audienceVoterId: {
            sessionId: input.sessionId,
            projectId: input.projectId,
            audienceVoterId: voter.id,
          },
        },
        create: {
          sessionId: input.sessionId,
          projectId: input.projectId,
          audienceVoterId: voter.id,
          score: input.score,
          isAudienceVote: true,
        },
        update: {
          score: input.score,
          votedAt: new Date(),
        },
      })

      return vote
    }),

  /**
   * Get audience voter stats (admin)
   */
  getAudienceVoterStats: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .query(async ({ ctx, input }) => {
      const voterCount = await ctx.prisma.audienceVoter.count({
        where: { sessionId: input.sessionId },
      })

      const voteCount = await ctx.prisma.liveVote.count({
        where: { sessionId: input.sessionId, isAudienceVote: true },
      })

      return { voterCount, voteCount }
    }),

  /**
   * Get public session info for audience voting page
   */
  getAudienceSession: publicProcedure
    .input(z.object({ sessionId: z.string() }))
    .query(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.findUniqueOrThrow({
        where: { id: input.sessionId },
        select: {
          id: true,
          status: true,
          currentProjectId: true,
          votingEndsAt: true,
          allowAudienceVotes: true,
          audienceVotingMode: true,
          audienceRequireId: true,
          audienceMaxFavorites: true,
          round: {
            select: {
              name: true,
              competition: {
                select: {
                  program: { select: { name: true, year: true } },
                },
              },
            },
          },
        },
      })

      let currentProject = null
      if (session.currentProjectId && session.status === 'IN_PROGRESS') {
        currentProject = await ctx.prisma.project.findUnique({
          where: { id: session.currentProjectId },
          select: { id: true, title: true, teamName: true },
        })
      }

      let timeRemaining = null
      if (session.votingEndsAt && session.status === 'IN_PROGRESS') {
        const remaining = new Date(session.votingEndsAt).getTime() - Date.now()
        timeRemaining = Math.max(0, Math.floor(remaining / 1000))
      }

      return {
        session,
        currentProject,
        timeRemaining,
      }
    }),

  /**
   * Get public results for a live voting session (no auth required)
   */
  getPublicResults: publicProcedure
    .input(z.object({ sessionId: z.string() }))
    .query(async ({ ctx, input }) => {
      const session = await ctx.prisma.liveVotingSession.findUniqueOrThrow({
        where: { id: input.sessionId },
        select: {
          id: true,
          status: true,
          currentProjectId: true,
          votingEndsAt: true,
          presentationSettingsJson: true,
          allowAudienceVotes: true,
          audienceVoteWeight: true,
        },
      })

      // Only return data if session is in progress or completed
      if (session.status !== 'IN_PROGRESS' && session.status !== 'COMPLETED') {
        return {
          session: {
            id: session.id,
            status: session.status,
            presentationSettings: session.presentationSettingsJson,
          },
          projects: [],
        }
      }

      // Get all votes grouped by project (anonymized - no user data)
      const scores = await ctx.prisma.liveVote.groupBy({
        by: ['projectId'],
        where: { sessionId: input.sessionId },
        _avg: { score: true },
        _count: true,
      })

      const projectIds = scores.map((s) => s.projectId)
      const projects = await ctx.prisma.project.findMany({
        where: { id: { in: projectIds } },
        select: { id: true, title: true, teamName: true },
      })

      const projectsWithScores = scores.map((score) => {
        const project = projects.find((p) => p.id === score.projectId)
        return {
          id: project?.id,
          title: project?.title,
          teamName: project?.teamName,
          averageScore: score._avg.score || 0,
          voteCount: score._count,
        }
      }).sort((a, b) => b.averageScore - a.averageScore)

      return {
        session: {
          id: session.id,
          status: session.status,
          currentProjectId: session.currentProjectId,
          votingEndsAt: session.votingEndsAt,
          presentationSettings: session.presentationSettingsJson,
          allowAudienceVotes: session.allowAudienceVotes,
        },
        projects: projectsWithScores,
      }
    }),
})
