import { router, adminProcedure, withAIRateLimit } from '../trpc'
import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import type { Prisma } from '@prisma/client'
import {
  parseRankingCriteria,
  executeAIRanking,
  quickRank as aiQuickRank,
  formulaRank,
  fetchAndRankCategory,
  type ParsedRankingRule,
} from '../services/ai-ranking'
import { logAudit } from '../utils/audit'
import type { EvaluationConfig } from '@/types/competition-configs'
import {
  computeBalanceContext,
  computeBalancedProjectScores,
  computePassRateContext,
  computeBalancedPassRates,
  type ScorePoint,
  type VotePoint,
} from '../services/juror-balance'

// ─── Local Types ───────────────────────────────────────────────────────────────

type ReorderEvent = {
  category: 'STARTUP' | 'BUSINESS_CONCEPT'
  orderedProjectIds: string[]
  reorderedBy: string
  reorderedAt: string
}

// ─── Zod Schemas ──────────────────────────────────────────────────────────────

const ParsedRuleSchema = z.object({
  step: z.number().int(),
  type: z.enum(['filter', 'sort', 'limit']),
  description: z.string(),
  field: z.enum(['pass_rate', 'avg_score', 'evaluator_count']).nullable(),
  operator: z.enum(['gte', 'lte', 'eq', 'top_n']).nullable(),
  value: z.number().nullable(),
  dataAvailable: z.boolean(),
})

// ─── Router ───────────────────────────────────────────────────────────────────

export const rankingRouter = router({
  /**
   * Parse natural-language criteria into structured rules (preview mode).
   * RANK-01, RANK-02, RANK-03 — admin reviews parsed rules before executing.
   */
  parseRankingCriteria: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        criteriaText: z.string().min(1).max(5000),
      }),
    )
    .mutation(async ({ ctx, input }): Promise<ParsedRankingRule[]> => {
      const rules = await parseRankingCriteria(input.criteriaText, ctx.user.id, input.roundId)

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'RANKING_CRITERIA_PARSED',
        entityType: 'Round',
        entityId: input.roundId,
        detailsJson: { ruleCount: rules.length },
      })

      return rules
    }),

  /**
   * Execute ranking using pre-parsed rules (confirmed mode).
   * Fetches both categories in parallel, persists a RankingSnapshot.
   * RANK-05, RANK-06, RANK-08.
   */
  executeRanking: adminProcedure
    .use(withAIRateLimit)
    .input(
      z.object({
        roundId: z.string(),
        criteriaText: z.string(),
        parsedRules: z.array(ParsedRuleSchema),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      // Cast to service type — validated by Zod above
      const rules = input.parsedRules as ParsedRankingRule[]

      // Fetch and rank both categories in parallel
      const [startup, concept] = await Promise.all([
        fetchAndRankCategory('STARTUP', rules, input.roundId, ctx.prisma, ctx.user.id),
        fetchAndRankCategory('BUSINESS_CONCEPT', rules, input.roundId, ctx.prisma, ctx.user.id),
      ])

      // Read criteria weights for snapshot audit trail
      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { configJson: true },
      })
      const evalConfig = (round.configJson as EvaluationConfig | null) ?? ({} as EvaluationConfig)
      const criteriaWeights = evalConfig.criteriaWeights ?? undefined

      // Persist snapshot — embed weights alongside rules for audit
      const parsedRulesWithWeights = { rules, weights: criteriaWeights } as unknown as Prisma.InputJsonValue
      const snapshot = await ctx.prisma.rankingSnapshot.create({
        data: {
          roundId: input.roundId,
          triggeredById: ctx.user.id,
          triggerType: 'MANUAL',
          mode: 'CONFIRMED',
          status: 'COMPLETED',
          criteriaText: input.criteriaText,
          parsedRulesJson: parsedRulesWithWeights,
          startupRankingJson: startup.rankedProjects as unknown as Prisma.InputJsonValue,
          conceptRankingJson: concept.rankedProjects as unknown as Prisma.InputJsonValue,
        },
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'RANKING_EXECUTED',
        entityType: 'RankingSnapshot',
        entityId: snapshot.id,
        detailsJson: {
          roundId: input.roundId,
          startupCount: startup.rankedProjects.length,
          conceptCount: concept.rankedProjects.length,
        },
      })

      return { snapshotId: snapshot.id, startup, concept }
    }),

  /**
   * Quick rank: parse criteria and execute in one step (RANK-04).
   * Persists a RankingSnapshot with mode=QUICK.
   */
  quickRank: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        criteriaText: z.string().min(1).max(5000),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const { startup, concept, parsedRules } = await aiQuickRank(
        input.criteriaText,
        input.roundId,
        ctx.prisma,
        ctx.user.id,
      )

      // Persist snapshot
      const snapshot = await ctx.prisma.rankingSnapshot.create({
        data: {
          roundId: input.roundId,
          triggeredById: ctx.user.id,
          triggerType: 'QUICK',
          mode: 'QUICK',
          status: 'COMPLETED',
          criteriaText: input.criteriaText,
          parsedRulesJson: parsedRules as unknown as Prisma.InputJsonValue,
          startupRankingJson: startup.rankedProjects as unknown as Prisma.InputJsonValue,
          conceptRankingJson: concept.rankedProjects as unknown as Prisma.InputJsonValue,
        },
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'RANKING_QUICK_EXECUTED',
        entityType: 'RankingSnapshot',
        entityId: snapshot.id,
        detailsJson: {
          roundId: input.roundId,
          startupCount: startup.rankedProjects.length,
          conceptCount: concept.rankedProjects.length,
        },
      })

      return { snapshotId: snapshot.id, startup, concept, parsedRules }
    }),

  /**
   * List all ranking snapshots for a round (Phase 2 dashboard prep).
   * Ordered by createdAt desc — newest first.
   */
  listSnapshots: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.rankingSnapshot.findMany({
        where: { roundId: input.roundId },
        orderBy: { createdAt: 'desc' },
        select: {
          id: true,
          triggeredById: true,
          triggerType: true,
          mode: true,
          status: true,
          criteriaText: true,
          tokensUsed: true,
          createdAt: true,
          triggeredBy: {
            select: { name: true },
          },
        },
      })
    }),

  /**
   * Get a single ranking snapshot by ID (Phase 2 dashboard prep).
   * Returns full snapshot including ranking results.
   */
  getSnapshot: adminProcedure
    .input(z.object({ snapshotId: z.string() }))
    .query(async ({ ctx, input }) => {
      const snapshot = await ctx.prisma.rankingSnapshot.findUnique({
        where: { id: input.snapshotId },
      })

      if (!snapshot) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: `Ranking snapshot ${input.snapshotId} not found`,
        })
      }

      return snapshot
    }),

  /** Persist admin drag-reorder to RankingSnapshot.reordersJson. Append-only — never overwrites old entries. DASH-02, DASH-03. */
  saveReorder: adminProcedure
    .input(
      z.object({
        snapshotId: z.string(),
        category: z.enum(['STARTUP', 'BUSINESS_CONCEPT']),
        orderedProjectIds: z.array(z.string()),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const snapshot = await ctx.prisma.rankingSnapshot.findUniqueOrThrow({
        where: { id: input.snapshotId },
        select: { reordersJson: true },
      })

      const existingReorders = (snapshot.reordersJson as ReorderEvent[] | null) ?? []

      const newReorder: ReorderEvent = {
        category: input.category,
        orderedProjectIds: input.orderedProjectIds,
        reorderedBy: ctx.user.id,
        reorderedAt: new Date().toISOString(),
      }

      await ctx.prisma.rankingSnapshot.update({
        where: { id: input.snapshotId },
        data: { reordersJson: [...existingReorders, newReorder] as unknown as Prisma.InputJsonValue },
      })

      return { ok: true }
    }),

  /** Clear all manual drag-reorders for a snapshot, optionally filtered by
   * category. The next dashboard render falls back to the system-computed
   * order. Append-only history of reorders is wiped — that's the point. */
  clearReorders: adminProcedure
    .input(
      z.object({
        snapshotId: z.string(),
        category: z.enum(['STARTUP', 'BUSINESS_CONCEPT']).optional(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const snapshot = await ctx.prisma.rankingSnapshot.findUniqueOrThrow({
        where: { id: input.snapshotId },
        select: { reordersJson: true },
      })
      const existing = (snapshot.reordersJson as ReorderEvent[] | null) ?? []
      const next = input.category
        ? existing.filter((r) => r.category !== input.category)
        : []
      await ctx.prisma.rankingSnapshot.update({
        where: { id: input.snapshotId },
        data: { reordersJson: next as unknown as Prisma.InputJsonValue },
      })
      return { ok: true, cleared: existing.length - next.length }
    }),

  /**
   * RANK-09 — Manual trigger for auto-rank (admin button on round detail page).
   * Reads ranking criteria from round configJson and executes quickRank.
   */
  triggerAutoRank: adminProcedure
    .use(withAIRateLimit)
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const { roundId } = input

      const round = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: roundId },
        select: { id: true, name: true, configJson: true, competition: { select: { id: true } } },
      })

      const config = (round.configJson as EvaluationConfig | null) ?? ({} as EvaluationConfig)
      const criteriaText = config?.rankingCriteria?.trim() || null
      const isFormulaMode = !criteriaText

      // Create a RUNNING snapshot so all admins see the in-progress indicator
      const snapshot = await ctx.prisma.rankingSnapshot.create({
        data: {
          roundId,
          triggeredById: ctx.user.id,
          triggerType: 'MANUAL',
          criteriaText: criteriaText ?? '',
          parsedRulesJson: {} as Prisma.InputJsonValue,
          mode: isFormulaMode ? 'FORMULA' : 'QUICK',
          status: 'RUNNING',
        },
      })

      try {
        let startup: { rankedProjects: unknown[] }
        let concept: { rankedProjects: unknown[] }
        let parsedRulesWithWeights: Prisma.InputJsonValue

        if (isFormulaMode) {
          // Formula-only: no LLM, pure math ranking
          const result = await formulaRank(roundId, ctx.prisma)
          startup = result.startup
          concept = result.concept
          const criteriaWeights = config.criteriaWeights ?? undefined
          parsedRulesWithWeights = {
            rules: [],
            weights: criteriaWeights,
            scoreWeight: config.scoreWeight ?? 5,
            passRateWeight: config.passRateWeight ?? 5,
          } as unknown as Prisma.InputJsonValue
        } else {
          // AI-assisted: parse criteria + rank with LLM
          const result = await aiQuickRank(criteriaText, roundId, ctx.prisma, ctx.user.id)
          startup = result.startup
          concept = result.concept
          const criteriaWeights = config.criteriaWeights ?? undefined
          parsedRulesWithWeights = { rules: result.parsedRules, weights: criteriaWeights } as unknown as Prisma.InputJsonValue
        }

        await ctx.prisma.rankingSnapshot.update({
          where: { id: snapshot.id },
          data: {
            status: 'COMPLETED',
            parsedRulesJson: parsedRulesWithWeights,
            startupRankingJson: startup.rankedProjects as unknown as Prisma.InputJsonValue,
            conceptRankingJson: concept.rankedProjects as unknown as Prisma.InputJsonValue,
          },
        })

        await logAudit({
          prisma: ctx.prisma,
          userId: ctx.user.id,
          action: 'RANKING_MANUAL_TRIGGERED',
          entityType: 'RankingSnapshot',
          entityId: snapshot.id,
          detailsJson: { roundId, mode: isFormulaMode ? 'FORMULA' : 'AI' },
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        })

        return { snapshotId: snapshot.id, startup, concept }
      } catch (err) {
        // Mark snapshot as FAILED so the indicator clears
        await ctx.prisma.rankingSnapshot.update({
          where: { id: snapshot.id },
          data: { status: 'FAILED' },
        })
        throw err
      }
    }),

  /**
   * RANK-10 — Retroactive scan: finds all active/closed rounds with autoRankOnComplete
   * configured but no RETROACTIVE snapshot yet, then executes ranking for each.
   * Runs sequentially to avoid hammering OpenAI.
   */
  retroactiveScan: adminProcedure
    .input(z.object({}))
    .mutation(async ({ ctx }) => {
      const rounds = await ctx.prisma.round.findMany({
        where: { status: { in: ['ROUND_ACTIVE', 'ROUND_CLOSED'] } },
        select: { id: true, name: true, configJson: true },
      })

      const results: Array<{ roundId: string; triggered: boolean; reason?: string }> = []

      for (const round of rounds) {
        const config = (round.configJson as EvaluationConfig | null) ?? ({} as EvaluationConfig)
        const autoRankEnabled = config?.autoRankOnComplete ?? false
        const criteriaText = config?.rankingCriteria ?? null

        if (!autoRankEnabled || !criteriaText) {
          results.push({ roundId: round.id, triggered: false, reason: 'auto-rank not configured' })
          continue
        }

        // Check if fully evaluated
        const [total, completed] = await Promise.all([
          ctx.prisma.assignment.count({ where: { roundId: round.id, isRequired: true } }),
          ctx.prisma.assignment.count({ where: { roundId: round.id, isRequired: true, isCompleted: true } }),
        ])

        if (total === 0 || total !== completed) {
          results.push({
            roundId: round.id,
            triggered: false,
            reason: `${completed}/${total} assignments complete`,
          })
          continue
        }

        // Check if a RETROACTIVE snapshot already exists
        const existing = await ctx.prisma.rankingSnapshot.findFirst({
          where: { roundId: round.id, triggerType: 'RETROACTIVE' },
          select: { id: true },
        })
        if (existing) {
          results.push({ roundId: round.id, triggered: false, reason: 'retroactive snapshot already exists' })
          continue
        }

        // Execute ranking sequentially to avoid rate limits
        try {
          const result = await aiQuickRank(criteriaText, round.id, ctx.prisma, ctx.user.id)
          await ctx.prisma.rankingSnapshot.create({
            data: {
              roundId: round.id,
              triggeredById: ctx.user.id,
              triggerType: 'RETROACTIVE',
              criteriaText,
              parsedRulesJson: result.parsedRules as unknown as Prisma.InputJsonValue,
              startupRankingJson: result.startup.rankedProjects as unknown as Prisma.InputJsonValue,
              conceptRankingJson: result.concept.rankedProjects as unknown as Prisma.InputJsonValue,
              mode: 'QUICK',
              status: 'COMPLETED',
            },
          })
          results.push({ roundId: round.id, triggered: true })
        } catch (err) {
          results.push({ roundId: round.id, triggered: false, reason: String(err) })
        }
      }

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'RANKING_RETROACTIVE_SCAN',
        entityType: 'Round',
        detailsJson: { results },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return {
        results,
        total: results.length,
        triggered: results.filter((r) => r.triggered).length,
      }
    }),

  /**
   * Get per-project evaluation scores for a round.
   * Returns a map of projectId → array of { jurorName, globalScore, binaryDecision }.
   * Used by the ranking dashboard to show individual juror scores inline.
   */
  roundEvaluationScores: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      // Find the advance-decision criterion ID across all active forms (shared + category-specific).
      // Current forms use type === 'advance' (one allowed per form). Legacy forms used
      // type === 'boolean' with a "move to the next stage" label.
      const activeForms = await ctx.prisma.evaluationForm.findMany({
        where: { roundId: input.roundId, isActive: true },
        select: { criteriaJson: true },
      })
      let boolCriterionId: string | null = null
      for (const f of activeForms) {
        const fc = (f.criteriaJson as Array<{
          id: string; label: string; type?: string
        }> | null) ?? []
        const found =
          fc.find((c) => c.type === 'advance') ??
          fc.find((c) => c.type === 'boolean' && c.label?.toLowerCase().includes('move to the next stage'))
        if (found) {
          boolCriterionId = found.id
          break
        }
      }

      const assignments = await ctx.prisma.assignment.findMany({
        where: {
          roundId: input.roundId,
          isRequired: true,
          evaluation: { status: 'SUBMITTED' },
        },
        select: {
          userId: true,
          projectId: true,
          user: { select: { name: true, email: true } },
          evaluation: {
            select: {
              globalScore: true,
              binaryDecision: true,
              criterionScoresJson: true,
            },
          },
        },
      })

      const byProject: Record<string, Array<{
        jurorName: string
        globalScore: number | null
        decision: boolean | null
      }>> = {}

      const balancePoints: ScorePoint[] = []
      const votePoints: VotePoint[] = []

      for (const a of assignments) {
        if (!a.evaluation) continue
        const list = byProject[a.projectId] ?? []

        // Resolve binary decision: column first, then criterion fallback
        let decision = a.evaluation.binaryDecision
        if (decision == null && boolCriterionId) {
          const scores = a.evaluation.criterionScoresJson as Record<string, unknown> | null
          if (scores) {
            const val = scores[boolCriterionId]
            if (typeof val === 'boolean') decision = val
            else if (val === 'true') decision = true
            else if (val === 'false') decision = false
          }
        }

        list.push({
          jurorName: a.user.name ?? a.user.email ?? 'Unknown',
          globalScore: a.evaluation.globalScore,
          decision,
        })
        byProject[a.projectId] = list

        if (a.evaluation.globalScore != null) {
          balancePoints.push({
            projectId: a.projectId,
            userId: a.userId,
            rawScore: a.evaluation.globalScore,
          })
        }

        if (decision !== null) {
          votePoints.push({
            projectId: a.projectId,
            userId: a.userId,
            vote: decision,
          })
        }
      }

      const balanceCtx = computeBalanceContext(balancePoints)
      const balancedByProject = computeBalancedProjectScores(balancePoints, balanceCtx)

      const passRateCtx = computePassRateContext(votePoints)
      const balancedPassRateByProject = computeBalancedPassRates(votePoints, passRateCtx)

      // Per-project: balanced score (1-10) + balanced pass rate (0-1).
      const balanced: Record<string, {
        rawAverage: number | null
        balancedAverage: number | null
        rawPassRate: number | null
        balancedPassRate: number | null
      }> = {}
      for (const [projectId, result] of balancedByProject.entries()) {
        balanced[projectId] = {
          rawAverage: result.rawAverage,
          balancedAverage: result.balancedAverage,
          rawPassRate: null,
          balancedPassRate: null,
        }
      }
      for (const [projectId, result] of balancedPassRateByProject.entries()) {
        const existing = balanced[projectId] ?? {
          rawAverage: null, balancedAverage: null, rawPassRate: null, balancedPassRate: null,
        }
        existing.rawPassRate = result.rawPassRate
        existing.balancedPassRate = result.balancedPassRate
        balanced[projectId] = existing
      }

      // Per-juror grading stats so the side panel can render each juror's
      // personal baseline and rescaled contribution.
      const jurorStats: Record<string, { mean: number; stddev: number; count: number }> = {}
      for (const [userId, s] of balanceCtx.jurorStats.entries()) {
        jurorStats[userId] = { mean: s.mean, stddev: s.stddev, count: s.count }
      }

      const jurorYesRates: Record<string, { yesRate: number; stddev: number; count: number }> = {}
      for (const [userId, s] of passRateCtx.jurorYesRates.entries()) {
        jurorYesRates[userId] = { yesRate: s.yesRate, stddev: s.stddev, count: s.count }
      }

      return {
        byProject,
        balanced,
        jurorStats,
        overallMean: balanceCtx.overallMean,
        overallStddev: balanceCtx.overallStddev,
        jurorYesRates,
        overallYesRate: passRateCtx.overallYesRate,
        overallYesStddev: passRateCtx.overallStddev,
      }
    }),
})
