import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { Prisma } from '@prisma/client'
import { router, protectedProcedure, adminProcedure } from '../trpc'
import { logAudit } from '../utils/audit'
import {
  applyAutoTagRules,
  aiInterpretCriteria,
  type AutoTagRule,
} from '../services/ai-award-eligibility'

export const specialAwardRouter = router({
  // ─── Admin Queries ──────────────────────────────────────────────────────

  /**
   * List awards for a program
   */
  list: protectedProcedure
    .input(
      z.object({
        programId: z.string().optional(),
      })
    )
    .query(async ({ ctx, input }) => {
      return ctx.prisma.specialAward.findMany({
        where: input.programId ? { programId: input.programId } : {},
        orderBy: { sortOrder: 'asc' },
        include: {
          _count: {
            select: {
              eligibilities: true,
              jurors: true,
              votes: true,
            },
          },
          winnerProject: {
            select: { id: true, title: true, teamName: true },
          },
        },
      })
    }),

  /**
   * Get award detail with stats
   */
  get: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const award = await ctx.prisma.specialAward.findUniqueOrThrow({
        where: { id: input.id },
        include: {
          _count: {
            select: {
              eligibilities: true,
              jurors: true,
              votes: true,
            },
          },
          winnerProject: {
            select: { id: true, title: true, teamName: true },
          },
          program: {
            select: { id: true, name: true, year: true },
          },
        },
      })

      // Count eligible projects
      const eligibleCount = await ctx.prisma.awardEligibility.count({
        where: { awardId: input.id, eligible: true },
      })

      return { ...award, eligibleCount }
    }),

  // ─── Admin Mutations ────────────────────────────────────────────────────

  /**
   * Create award
   */
  create: adminProcedure
    .input(
      z.object({
        programId: z.string(),
        name: z.string().min(1),
        description: z.string().optional(),
        criteriaText: z.string().optional(),
        useAiEligibility: z.boolean().optional(),
        scoringMode: z.enum(['PICK_WINNER', 'RANKED', 'SCORED']),
        maxRankedPicks: z.number().int().min(1).max(20).optional(),
        autoTagRulesJson: z.record(z.unknown()).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const maxOrder = await ctx.prisma.specialAward.aggregate({
        where: { programId: input.programId },
        _max: { sortOrder: true },
      })

      const award = await ctx.prisma.specialAward.create({
        data: {
          programId: input.programId,
          name: input.name,
          description: input.description,
          criteriaText: input.criteriaText,
          useAiEligibility: input.useAiEligibility ?? true,
          scoringMode: input.scoringMode,
          maxRankedPicks: input.maxRankedPicks,
          autoTagRulesJson: input.autoTagRulesJson as Prisma.InputJsonValue ?? undefined,
          sortOrder: (maxOrder._max.sortOrder || 0) + 1,
        },
      })

      await logAudit({
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'SpecialAward',
        entityId: award.id,
        detailsJson: { name: input.name, scoringMode: input.scoringMode },
      })

      return award
    }),

  /**
   * Update award config
   */
  update: adminProcedure
    .input(
      z.object({
        id: z.string(),
        name: z.string().min(1).optional(),
        description: z.string().optional(),
        criteriaText: z.string().optional(),
        useAiEligibility: z.boolean().optional(),
        scoringMode: z.enum(['PICK_WINNER', 'RANKED', 'SCORED']).optional(),
        maxRankedPicks: z.number().int().min(1).max(20).optional(),
        autoTagRulesJson: z.record(z.unknown()).optional(),
        votingStartAt: z.date().optional(),
        votingEndAt: z.date().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id, autoTagRulesJson, ...rest } = input
      const award = await ctx.prisma.specialAward.update({
        where: { id },
        data: {
          ...rest,
          ...(autoTagRulesJson !== undefined && { autoTagRulesJson: autoTagRulesJson as Prisma.InputJsonValue }),
        },
      })

      await logAudit({
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'SpecialAward',
        entityId: id,
      })

      return award
    }),

  /**
   * Delete award
   */
  delete: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      await ctx.prisma.specialAward.delete({ where: { id: input.id } })

      await logAudit({
        userId: ctx.user.id,
        action: 'DELETE',
        entityType: 'SpecialAward',
        entityId: input.id,
      })
    }),

  /**
   * Update award status
   */
  updateStatus: adminProcedure
    .input(
      z.object({
        id: z.string(),
        status: z.enum([
          'DRAFT',
          'NOMINATIONS_OPEN',
          'VOTING_OPEN',
          'CLOSED',
          'ARCHIVED',
        ]),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const current = await ctx.prisma.specialAward.findUniqueOrThrow({
        where: { id: input.id },
        select: { status: true },
      })

      const award = await ctx.prisma.specialAward.update({
        where: { id: input.id },
        data: { status: input.status },
      })

      await logAudit({
        userId: ctx.user.id,
        action: 'UPDATE_STATUS',
        entityType: 'SpecialAward',
        entityId: input.id,
        detailsJson: {
          previousStatus: current.status,
          newStatus: input.status,
        },
      })

      return award
    }),

  // ─── Eligibility ────────────────────────────────────────────────────────

  /**
   * Run auto-tag + AI eligibility
   */
  runEligibility: adminProcedure
    .input(z.object({
      awardId: z.string(),
      includeSubmitted: z.boolean().optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      const award = await ctx.prisma.specialAward.findUniqueOrThrow({
        where: { id: input.awardId },
        include: { program: true },
      })

      // Get projects in the program's rounds
      const statusFilter = input.includeSubmitted
        ? (['SUBMITTED', 'ELIGIBLE', 'ASSIGNED', 'SEMIFINALIST', 'FINALIST'] as const)
        : (['ELIGIBLE', 'ASSIGNED', 'SEMIFINALIST', 'FINALIST'] as const)
      const projects = await ctx.prisma.project.findMany({
        where: {
          round: { programId: award.programId },
          status: { in: [...statusFilter] },
        },
        select: {
          id: true,
          title: true,
          description: true,
          competitionCategory: true,
          country: true,
          geographicZone: true,
          tags: true,
          oceanIssue: true,
        },
      })

      if (projects.length === 0) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'No eligible projects found',
        })
      }

      // Phase 1: Auto-tag rules (deterministic)
      const autoTagRules = award.autoTagRulesJson as unknown as AutoTagRule[] | null
      let autoResults: Map<string, boolean> | undefined
      if (autoTagRules && Array.isArray(autoTagRules) && autoTagRules.length > 0) {
        autoResults = applyAutoTagRules(autoTagRules, projects)
      }

      // Phase 2: AI interpretation (if criteria text exists AND AI eligibility is enabled)
      let aiResults: Map<string, { eligible: boolean; confidence: number; reasoning: string }> | undefined
      if (award.criteriaText && award.useAiEligibility) {
        const aiEvals = await aiInterpretCriteria(award.criteriaText, projects)
        aiResults = new Map(
          aiEvals.map((e) => [
            e.projectId,
            { eligible: e.eligible, confidence: e.confidence, reasoning: e.reasoning },
          ])
        )
      }

      // Combine results: auto-tag AND AI must agree (or just one if only one configured)
      const eligibilities = projects.map((project) => {
        const autoEligible = autoResults?.get(project.id) ?? true
        const aiEval = aiResults?.get(project.id)
        const aiEligible = aiEval?.eligible ?? true

        const eligible = autoEligible && aiEligible
        const method = autoResults && aiResults ? 'AUTO' : autoResults ? 'AUTO' : 'MANUAL'

        return {
          projectId: project.id,
          eligible,
          method,
          aiReasoningJson: aiEval
            ? { confidence: aiEval.confidence, reasoning: aiEval.reasoning }
            : null,
        }
      })

      // Upsert eligibilities
      await ctx.prisma.$transaction(
        eligibilities.map((e) =>
          ctx.prisma.awardEligibility.upsert({
            where: {
              awardId_projectId: {
                awardId: input.awardId,
                projectId: e.projectId,
              },
            },
            create: {
              awardId: input.awardId,
              projectId: e.projectId,
              eligible: e.eligible,
              method: e.method as 'AUTO' | 'MANUAL',
              aiReasoningJson: e.aiReasoningJson ?? undefined,
            },
            update: {
              eligible: e.eligible,
              method: e.method as 'AUTO' | 'MANUAL',
              aiReasoningJson: e.aiReasoningJson ?? undefined,
              // Clear overrides
              overriddenBy: null,
              overriddenAt: null,
            },
          })
        )
      )

      const eligibleCount = eligibilities.filter((e) => e.eligible).length

      await logAudit({
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'SpecialAward',
        entityId: input.awardId,
        detailsJson: {
          action: 'RUN_ELIGIBILITY',
          totalProjects: projects.length,
          eligible: eligibleCount,
        },
      })

      return {
        total: projects.length,
        eligible: eligibleCount,
        ineligible: projects.length - eligibleCount,
      }
    }),

  /**
   * List eligible projects
   */
  listEligible: protectedProcedure
    .input(
      z.object({
        awardId: z.string(),
        eligibleOnly: z.boolean().default(false),
        page: z.number().int().min(1).default(1),
        perPage: z.number().int().min(1).max(100).default(50),
      })
    )
    .query(async ({ ctx, input }) => {
      const { awardId, eligibleOnly, page, perPage } = input
      const skip = (page - 1) * perPage

      const where: Record<string, unknown> = { awardId }
      if (eligibleOnly) where.eligible = true

      const [eligibilities, total] = await Promise.all([
        ctx.prisma.awardEligibility.findMany({
          where,
          skip,
          take: perPage,
          include: {
            project: {
              select: {
                id: true,
                title: true,
                teamName: true,
                competitionCategory: true,
                country: true,
                tags: true,
              },
            },
          },
          orderBy: { project: { title: 'asc' } },
        }),
        ctx.prisma.awardEligibility.count({ where }),
      ])

      return { eligibilities, total, page, perPage, totalPages: Math.ceil(total / perPage) }
    }),

  /**
   * Manual eligibility override
   */
  setEligibility: adminProcedure
    .input(
      z.object({
        awardId: z.string(),
        projectId: z.string(),
        eligible: z.boolean(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      await ctx.prisma.awardEligibility.upsert({
        where: {
          awardId_projectId: {
            awardId: input.awardId,
            projectId: input.projectId,
          },
        },
        create: {
          awardId: input.awardId,
          projectId: input.projectId,
          eligible: input.eligible,
          method: 'MANUAL',
          overriddenBy: ctx.user.id,
          overriddenAt: new Date(),
        },
        update: {
          eligible: input.eligible,
          overriddenBy: ctx.user.id,
          overriddenAt: new Date(),
        },
      })
    }),

  // ─── Jurors ─────────────────────────────────────────────────────────────

  /**
   * List jurors for an award
   */
  listJurors: protectedProcedure
    .input(z.object({ awardId: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.awardJuror.findMany({
        where: { awardId: input.awardId },
        include: {
          user: {
            select: {
              id: true,
              name: true,
              email: true,
              role: true,
              profileImageKey: true,
              profileImageProvider: true,
            },
          },
        },
      })
    }),

  /**
   * Add juror
   */
  addJuror: adminProcedure
    .input(
      z.object({
        awardId: z.string(),
        userId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      return ctx.prisma.awardJuror.create({
        data: {
          awardId: input.awardId,
          userId: input.userId,
        },
      })
    }),

  /**
   * Remove juror
   */
  removeJuror: adminProcedure
    .input(
      z.object({
        awardId: z.string(),
        userId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      await ctx.prisma.awardJuror.delete({
        where: {
          awardId_userId: {
            awardId: input.awardId,
            userId: input.userId,
          },
        },
      })
    }),

  /**
   * Bulk add jurors
   */
  bulkAddJurors: adminProcedure
    .input(
      z.object({
        awardId: z.string(),
        userIds: z.array(z.string()),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const data = input.userIds.map((userId) => ({
        awardId: input.awardId,
        userId,
      }))

      await ctx.prisma.awardJuror.createMany({
        data,
        skipDuplicates: true,
      })

      return { added: input.userIds.length }
    }),

  // ─── Jury Queries ───────────────────────────────────────────────────────

  /**
   * Get awards where current user is a juror
   */
  getMyAwards: protectedProcedure.query(async ({ ctx }) => {
    const jurorships = await ctx.prisma.awardJuror.findMany({
      where: { userId: ctx.user.id },
      include: {
        award: {
          include: {
            _count: {
              select: { eligibilities: { where: { eligible: true } } },
            },
          },
        },
      },
    })

    return jurorships.map((j) => j.award)
  }),

  /**
   * Get award detail for voting (jury view)
   */
  getMyAwardDetail: protectedProcedure
    .input(z.object({ awardId: z.string() }))
    .query(async ({ ctx, input }) => {
      // Verify user is a juror
      const juror = await ctx.prisma.awardJuror.findUnique({
        where: {
          awardId_userId: {
            awardId: input.awardId,
            userId: ctx.user.id,
          },
        },
      })

      if (!juror) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'You are not a juror for this award',
        })
      }

      const award = await ctx.prisma.specialAward.findUniqueOrThrow({
        where: { id: input.awardId },
      })

      // Get eligible projects
      const eligibleProjects = await ctx.prisma.awardEligibility.findMany({
        where: { awardId: input.awardId, eligible: true },
        include: {
          project: {
            select: {
              id: true,
              title: true,
              teamName: true,
              description: true,
              competitionCategory: true,
              country: true,
              tags: true,
            },
          },
        },
      })

      // Get user's existing votes
      const myVotes = await ctx.prisma.awardVote.findMany({
        where: { awardId: input.awardId, userId: ctx.user.id },
      })

      return {
        award,
        projects: eligibleProjects.map((e) => e.project),
        myVotes,
      }
    }),

  // ─── Voting ─────────────────────────────────────────────────────────────

  /**
   * Submit vote (PICK_WINNER or RANKED)
   */
  submitVote: protectedProcedure
    .input(
      z.object({
        awardId: z.string(),
        votes: z.array(
          z.object({
            projectId: z.string(),
            rank: z.number().int().min(1).optional(),
          })
        ),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify juror
      const juror = await ctx.prisma.awardJuror.findUnique({
        where: {
          awardId_userId: {
            awardId: input.awardId,
            userId: ctx.user.id,
          },
        },
      })

      if (!juror) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'You are not a juror for this award',
        })
      }

      // Verify award is open for voting
      const award = await ctx.prisma.specialAward.findUniqueOrThrow({
        where: { id: input.awardId },
      })

      if (award.status !== 'VOTING_OPEN') {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Voting is not currently open for this award',
        })
      }

      // Delete existing votes and create new ones
      await ctx.prisma.$transaction([
        ctx.prisma.awardVote.deleteMany({
          where: { awardId: input.awardId, userId: ctx.user.id },
        }),
        ...input.votes.map((vote) =>
          ctx.prisma.awardVote.create({
            data: {
              awardId: input.awardId,
              userId: ctx.user.id,
              projectId: vote.projectId,
              rank: vote.rank,
            },
          })
        ),
      ])

      await logAudit({
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'AwardVote',
        entityId: input.awardId,
        detailsJson: {
          awardId: input.awardId,
          voteCount: input.votes.length,
          scoringMode: award.scoringMode,
        },
      })

      return { submitted: input.votes.length }
    }),

  // ─── Results ────────────────────────────────────────────────────────────

  /**
   * Get aggregated vote results
   */
  getVoteResults: adminProcedure
    .input(z.object({ awardId: z.string() }))
    .query(async ({ ctx, input }) => {
      const award = await ctx.prisma.specialAward.findUniqueOrThrow({
        where: { id: input.awardId },
      })

      const votes = await ctx.prisma.awardVote.findMany({
        where: { awardId: input.awardId },
        include: {
          project: {
            select: { id: true, title: true, teamName: true },
          },
          user: {
            select: { id: true, name: true, email: true },
          },
        },
      })

      const jurorCount = await ctx.prisma.awardJuror.count({
        where: { awardId: input.awardId },
      })

      const votedJurorCount = new Set(votes.map((v) => v.userId)).size

      // Tally by scoring mode
      const projectTallies = new Map<
        string,
        { project: { id: string; title: string; teamName: string | null }; votes: number; points: number }
      >()

      for (const vote of votes) {
        const existing = projectTallies.get(vote.projectId) || {
          project: vote.project,
          votes: 0,
          points: 0,
        }
        existing.votes += 1
        if (award.scoringMode === 'RANKED' && vote.rank) {
          existing.points += (award.maxRankedPicks || 5) - vote.rank + 1
        } else {
          existing.points += 1
        }
        projectTallies.set(vote.projectId, existing)
      }

      const ranked = Array.from(projectTallies.values()).sort(
        (a, b) => b.points - a.points
      )

      return {
        scoringMode: award.scoringMode,
        jurorCount,
        votedJurorCount,
        results: ranked,
        winnerId: award.winnerProjectId,
        winnerOverridden: award.winnerOverridden,
      }
    }),

  /**
   * Set/override winner
   */
  setWinner: adminProcedure
    .input(
      z.object({
        awardId: z.string(),
        projectId: z.string(),
        overridden: z.boolean().default(false),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const previous = await ctx.prisma.specialAward.findUniqueOrThrow({
        where: { id: input.awardId },
        select: { winnerProjectId: true },
      })

      const award = await ctx.prisma.specialAward.update({
        where: { id: input.awardId },
        data: {
          winnerProjectId: input.projectId,
          winnerOverridden: input.overridden,
          winnerOverriddenBy: input.overridden ? ctx.user.id : null,
        },
      })

      await logAudit({
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'SpecialAward',
        entityId: input.awardId,
        detailsJson: {
          action: 'SET_AWARD_WINNER',
          previousWinner: previous.winnerProjectId,
          newWinner: input.projectId,
          overridden: input.overridden,
        },
      })

      return award
    }),
})
