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

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: {
              program: { select: { name: true, year: true } },
              projects: {
                where: { status: { in: ['FINALIST', 'SEMIFINALIST'] } },
                select: { id: true, title: true, teamName: true },
              },
            },
          },
        },
      })

      if (!session) {
        // Create session
        session = await ctx.prisma.liveVotingSession.create({
          data: {
            roundId: input.roundId,
          },
          include: {
            round: {
              include: {
                program: { select: { name: true, year: true } },
                projects: {
                  where: { status: { in: ['FINALIST', 'SEMIFINALIST'] } },
                  select: { id: true, title: true, teamName: true },
                },
              },
            },
          },
        })
      }

      // Get current votes if voting is in progress
      let currentVotes: { userId: string; 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
      }

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

  /**
   * 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: {
              program: { select: { name: true, year: true } },
            },
          },
        },
      })

      // Get current project if in progress
      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 },
        })
      }

      // Get user's vote for current project
      let userVote = null
      if (session.currentProjectId) {
        userVote = await ctx.prisma.liveVote.findFirst({
          where: {
            sessionId: session.id,
            projectId: session.currentProjectId,
            userId: ctx.user.id,
          },
        })
      }

      // Calculate time remaining
      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,
        },
        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: {
              program: { select: { name: true, year: true } },
            },
          },
        },
      })

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

      // Sort by order
      const sortedProjects = projectOrder
        .map((id) => projects.find((p) => p.id === id))
        .filter(Boolean)

      // Get scores for each project
      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
    }),

  /**
   * 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
   */
  vote: protectedProcedure
    .input(
      z.object({
        sessionId: z.string(),
        projectId: z.string(),
        score: z.number().int().min(1).max(10),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify session is in progress
      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.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',
        })
      }

      // Upsert vote (allow vote change during window)
      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: input.score,
        },
        update: {
          score: input.score,
          votedAt: new Date(),
        },
      })

      return vote
    }),

  /**
   * Get results for a session (with weighted jury + audience scoring)
   */
  getResults: 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: {
              program: { select: { name: true, year: true } },
            },
          },
        },
      })

      const audienceWeight = session.audienceVoteWeight || 0
      const juryWeight = 1 - audienceWeight

      // 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]))

      // 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 = audienceWeight > 0 && audienceSc
            ? juryAvg * juryWeight + audienceAvg * audienceWeight
            : juryAvg

          return {
            project,
            juryAverage: juryAvg,
            juryVoteCount: jurySc._count,
            audienceAverage: audienceAvg,
            audienceVoteCount: audienceSc?._count || 0,
            weightedTotal,
          }
        })
        .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,
      }
    }),

  /**
   * 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(),
      })
    )
    .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 {
        // Audit log errors should never break the operation
      }

      return session
    }),

  /**
   * Cast an audience vote
   */
  castAudienceVote: protectedProcedure
    .input(
      z.object({
        sessionId: z.string(),
        projectId: z.string(),
        score: z.number().int().min(1).max(10),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // 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
      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: input.score,
          isAudienceVote: true,
        },
        update: {
          score: input.score,
          votedAt: new Date(),
        },
      })

      return vote
    }),

  /**
   * 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,
      }
    }),
})
