import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, adminProcedure, juryProcedure, protectedProcedure } from '../trpc'
import { logAudit } from '@/server/utils/audit'
import {
  createSession,
  openVoting,
  closeVoting,
  submitVote,
  aggregateVotes,
  initRunoff,
  adminDecide,
  finalizeResults,
  updateParticipantStatus,
  getSessionWithVotes,
} from '../services/deliberation'

const categoryEnum = z.enum([
  'STARTUP',
  'BUSINESS_CONCEPT',
])

const deliberationModeEnum = z.enum(['SINGLE_WINNER_VOTE', 'FULL_RANKING'])

const tieBreakMethodEnum = z.enum(['TIE_RUNOFF', 'TIE_ADMIN_DECIDES', 'SCORE_FALLBACK'])

const participantStatusEnum = z.enum([
  'REQUIRED',
  'ABSENT_EXCUSED',
  'REPLACED',
  'REPLACEMENT_ACTIVE',
])

export const deliberationRouter = router({
  /**
   * Create a new deliberation session with participants
   */
  createSession: adminProcedure
    .input(
      z.object({
        competitionId: z.string(),
        roundId: z.string(),
        category: categoryEnum,
        mode: deliberationModeEnum,
        tieBreakMethod: tieBreakMethodEnum,
        showCollectiveRankings: z.boolean().default(false),
        showPriorJuryData: z.boolean().default(false),
        participantUserIds: z.array(z.string()).min(1),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const session = await createSession(input, ctx.prisma)

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'DeliberationSession',
        entityId: session.id,
        detailsJson: {
          competitionId: input.competitionId,
          roundId: input.roundId,
          category: input.category,
          mode: input.mode,
          participantCount: input.participantUserIds.length,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return session
    }),

  /**
   * Open voting: DELIB_OPEN → VOTING
   */
  openVoting: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const result = await openVoting(input.sessionId, ctx.user.id, ctx.prisma)
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to open voting',
        })
      }
      return result
    }),

  /**
   * Close voting: VOTING → TALLYING
   */
  closeVoting: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const result = await closeVoting(input.sessionId, ctx.user.id, ctx.prisma)
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to close voting',
        })
      }
      return result
    }),

  /**
   * Submit a vote (jury member)
   */
  submitVote: juryProcedure
    .input(
      z.object({
        sessionId: z.string(),
        juryMemberId: z.string(),
        projectId: z.string(),
        rank: z.number().int().min(1).optional(),
        isWinnerPick: z.boolean().optional(),
        runoffRound: z.number().int().min(0).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Enforce that jury members can only vote as themselves
      if (input.juryMemberId !== ctx.user.id) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'You can only submit votes as yourself',
        })
      }

      const vote = await submitVote(input, ctx.prisma)

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'DeliberationVote',
        entityId: input.sessionId,
        detailsJson: {
          sessionId: input.sessionId,
          projectId: input.projectId,
          rank: input.rank,
          isWinnerPick: input.isWinnerPick,
          runoffRound: input.runoffRound,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return vote
    }),

  /**
   * Aggregate votes for a session
   */
  aggregate: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .query(async ({ ctx, input }) => {
      const result = await aggregateVotes(input.sessionId, ctx.prisma)
      // Enrich rankings with project titles
      const projectIds = result.rankings.map((r) => r.projectId)
      const projects = projectIds.length > 0
        ? await ctx.prisma.project.findMany({
            where: { id: { in: projectIds } },
            select: { id: true, title: true, teamName: true },
          })
        : []
      const projectMap = new Map(projects.map((p) => [p.id, p]))
      return {
        ...result,
        rankings: result.rankings.map((r) => ({
          ...r,
          projectTitle: projectMap.get(r.projectId)?.title ?? 'Unknown Project',
          teamName: projectMap.get(r.projectId)?.teamName ?? '',
        })),
      }
    }),

  /**
   * Initiate a runoff: TALLYING → RUNOFF
   */
  initRunoff: adminProcedure
    .input(
      z.object({
        sessionId: z.string(),
        tiedProjectIds: z.array(z.string()).min(2),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const result = await initRunoff(
        input.sessionId,
        input.tiedProjectIds,
        ctx.user.id,
        ctx.prisma,
      )
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to initiate runoff',
        })
      }
      return result
    }),

  /**
   * Admin override: directly set final rankings
   */
  adminDecide: adminProcedure
    .input(
      z.object({
        sessionId: z.string(),
        rankings: z.array(
          z.object({
            projectId: z.string(),
            rank: z.number().int().min(1),
          })
        ).min(1),
        reason: z.string().min(1).max(2000),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const result = await adminDecide(
        input.sessionId,
        input.rankings,
        input.reason,
        ctx.user.id,
        ctx.prisma,
      )
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to admin-decide',
        })
      }
      return result
    }),

  /**
   * Finalize results: TALLYING → DELIB_LOCKED
   */
  finalize: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const result = await finalizeResults(input.sessionId, ctx.user.id, ctx.prisma)
      if (!result.success) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: result.errors?.join('; ') ?? 'Failed to finalize results',
        })
      }
      return result
    }),

  /**
   * Get session with votes, results, and participants.
   * Redacts juror identities for non-admin users when session flags are off.
   */
  getSession: protectedProcedure
    .input(z.object({ sessionId: z.string() }))
    .query(async ({ ctx, input }) => {
      const session = await getSessionWithVotes(input.sessionId, ctx.prisma)
      if (!session) {
        throw new TRPCError({ code: 'NOT_FOUND', message: 'Session not found' })
      }

      const isAdmin = ctx.user.role === 'SUPER_ADMIN' || ctx.user.role === 'PROGRAM_ADMIN'
      if (isAdmin) return session

      // Non-admin: enforce visibility flags
      if (!session.showCollectiveRankings) {
        // Anonymize juror identity on votes — only show own votes with identity
        session.votes = session.votes.map((v: any, i: number) => {
          const isOwn = v.juryMember?.user?.id === ctx.user.id
          if (isOwn) return v
          return {
            ...v,
            juryMember: {
              ...v.juryMember,
              user: { id: `anon-${i}`, name: `Juror ${i + 1}`, email: '' },
            },
          }
        })
        // Anonymize participants
        session.participants = session.participants.map((p: any, i: number) => {
          const isOwn = p.user?.user?.id === ctx.user.id
          if (isOwn) return p
          return {
            ...p,
            user: p.user
              ? { ...p.user, user: { id: `anon-${i}`, name: `Juror ${i + 1}`, email: '' } }
              : p.user,
          }
        })
      }

      return session
    }),

  /**
   * List deliberation sessions for a competition
   */
  listSessions: adminProcedure
    .input(z.object({ competitionId: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.deliberationSession.findMany({
        where: {
          round: { competitionId: input.competitionId },
        },
        include: {
          round: { select: { id: true, name: true, roundType: true } },
          _count: { select: { votes: true, participants: true } },
          participants: {
            select: { userId: true },
          },
        },
        orderBy: { createdAt: 'desc' },
      })
    }),

  /**
   * Update participant status (mark absent, replace, etc.)
   */
  updateParticipant: adminProcedure
    .input(
      z.object({
        sessionId: z.string(),
        userId: z.string(),
        status: participantStatusEnum,
        replacedById: z.string().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const result = await updateParticipantStatus(
        input.sessionId,
        input.userId,
        input.status,
        input.replacedById,
        ctx.user.id,
        ctx.prisma,
      )

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'DeliberationParticipant',
        entityId: input.sessionId,
        detailsJson: {
          sessionId: input.sessionId,
          targetUserId: input.userId,
          status: input.status,
          replacedById: input.replacedById,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return result
    }),
})
