import { z } from 'zod'
import { router, adminProcedure, observerProcedure } from '../trpc'
import { logAudit } from '../utils/audit'

export const exportRouter = router({
  /**
   * Export evaluations as CSV data
   */
  evaluations: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        includeDetails: z.boolean().default(true),
      })
    )
    .query(async ({ ctx, input }) => {
      const evaluations = await ctx.prisma.evaluation.findMany({
        where: {
          status: 'SUBMITTED',
          assignment: { roundId: input.roundId },
        },
        include: {
          assignment: {
            include: {
              user: { select: { name: true, email: true } },
              project: { select: { title: true, teamName: true, tags: true } },
            },
          },
          form: { select: { criteriaJson: true } },
        },
        orderBy: [
          { assignment: { project: { title: 'asc' } } },
          { submittedAt: 'asc' },
        ],
      })

      // Get criteria labels from form
      const criteriaLabels: Record<string, string> = {}
      if (evaluations.length > 0) {
        const criteria = evaluations[0].form.criteriaJson as Array<{
          id: string
          label: string
        }>
        criteria.forEach((c) => {
          criteriaLabels[c.id] = c.label
        })
      }

      // Build export data
      const data = evaluations.map((e) => {
        const scores = e.criterionScoresJson as Record<string, number> | null
        const criteriaScores: Record<string, number | null> = {}

        Object.keys(criteriaLabels).forEach((id) => {
          criteriaScores[criteriaLabels[id]] = scores?.[id] ?? null
        })

        return {
          projectTitle: e.assignment.project.title,
          teamName: e.assignment.project.teamName,
          tags: e.assignment.project.tags.join(', '),
          jurorName: e.assignment.user.name,
          jurorEmail: e.assignment.user.email,
          ...criteriaScores,
          globalScore: e.globalScore,
          decision: e.binaryDecision ? 'Yes' : 'No',
          feedback: input.includeDetails ? e.feedbackText : null,
          submittedAt: e.submittedAt?.toISOString(),
        }
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'EXPORT',
        entityType: 'Evaluation',
        detailsJson: { roundId: input.roundId, count: data.length },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return {
        data,
        columns: [
          'projectTitle',
          'teamName',
          'tags',
          'jurorName',
          'jurorEmail',
          ...Object.values(criteriaLabels),
          'globalScore',
          'decision',
          ...(input.includeDetails ? ['feedback'] : []),
          'submittedAt',
        ],
      }
    }),

  /**
   * Export project scores summary with per-criterion averages
   */
  projectScores: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      // Fetch all active evaluation forms for this round (shared + category-specific)
      const activeForms = await ctx.prisma.evaluationForm.findMany({
        where: { roundId: input.roundId, isActive: true },
        select: { criteriaJson: true },
      })
      // Merge criteria across all forms, deduplicating by criterion id
      const seenCriterionIds = new Set<string>()
      const criteria: Array<{ id: string; label: string; type?: string }> = []
      for (const f of activeForms) {
        const fc = (f.criteriaJson as Array<{ id: string; label: string; type?: string }> | null) ?? []
        for (const c of fc) {
          if (!seenCriterionIds.has(c.id)) {
            seenCriterionIds.add(c.id)
            criteria.push(c)
          }
        }
      }
      const numericCriteria = criteria.filter((c) => !c.type || c.type === 'numeric')

      const projects = await ctx.prisma.project.findMany({
        where: {
          assignments: { some: { roundId: input.roundId } },
        },
        include: {
          assignments: {
            where: { roundId: input.roundId },
            include: {
              evaluation: {
                where: { status: 'SUBMITTED' },
                select: {
                  globalScore: true,
                  binaryDecision: true,
                  criterionScoresJson: true,
                },
              },
            },
          },
        },
        orderBy: { title: 'asc' },
      })

      const data = projects.map((p) => {
        const evaluations = p.assignments
          .map((a) => a.evaluation)
          .filter((e) => e !== null)

        const globalScores = evaluations
          .map((e) => e?.globalScore)
          .filter((s): s is number => s !== null)

        const yesVotes = evaluations.filter(
          (e) => e?.binaryDecision === true
        ).length

        // Per-criterion averages
        const criterionAvgs: Record<string, string | null> = {}
        for (const c of numericCriteria) {
          const values: number[] = []
          for (const e of evaluations) {
            const scores = e?.criterionScoresJson as Record<string, number> | null
            if (scores && typeof scores[c.id] === 'number') values.push(scores[c.id])
          }
          criterionAvgs[c.label] = values.length > 0
            ? (values.reduce((a, b) => a + b, 0) / values.length).toFixed(2)
            : null
        }

        return {
          title: p.title,
          teamName: p.teamName,
          category: p.competitionCategory ?? '',
          country: p.country ?? '',
          status: p.status,
          tags: p.tags.join(', '),
          totalEvaluations: evaluations.length,
          averageScore:
            globalScores.length > 0
              ? (
                  globalScores.reduce((a, b) => a + b, 0) / globalScores.length
                ).toFixed(2)
              : null,
          minScore: globalScores.length > 0 ? Math.min(...globalScores) : null,
          maxScore: globalScores.length > 0 ? Math.max(...globalScores) : null,
          ...criterionAvgs,
          yesVotes,
          noVotes: evaluations.length - yesVotes,
          yesPercentage:
            evaluations.length > 0
              ? ((yesVotes / evaluations.length) * 100).toFixed(1)
              : null,
        }
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'EXPORT',
        entityType: 'ProjectScores',
        detailsJson: { roundId: input.roundId, count: data.length },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return {
        data,
        columns: [
          'title',
          'teamName',
          'category',
          'country',
          'status',
          'tags',
          'totalEvaluations',
          'averageScore',
          'minScore',
          'maxScore',
          ...numericCriteria.map((c) => c.label),
          'yesVotes',
          'noVotes',
          'yesPercentage',
        ],
      }
    }),

  /**
   * Export assignments
   */
  assignments: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      const assignments = await ctx.prisma.assignment.findMany({
        where: { roundId: input.roundId },
        include: {
          user: { select: { name: true, email: true } },
          project: { select: { title: true, teamName: true } },
          evaluation: { select: { status: true, submittedAt: true } },
        },
        orderBy: [{ project: { title: 'asc' } }, { user: { name: 'asc' } }],
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'EXPORT',
        entityType: 'Assignment',
        detailsJson: { roundId: input.roundId, count: assignments.length, exportType: 'assignments' },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      const data = assignments.map((a) => ({
        projectTitle: a.project.title,
        teamName: a.project.teamName,
        jurorName: a.user.name,
        jurorEmail: a.user.email,
        method: a.method,
        isRequired: a.isRequired ? 'Yes' : 'No',
        isCompleted: a.isCompleted ? 'Yes' : 'No',
        evaluationStatus: a.evaluation?.status ?? 'NOT_STARTED',
        submittedAt: a.evaluation?.submittedAt?.toISOString() ?? null,
        assignedAt: a.createdAt.toISOString(),
      }))

      return {
        data,
        columns: [
          'projectTitle',
          'teamName',
          'jurorName',
          'jurorEmail',
          'method',
          'isRequired',
          'isCompleted',
          'evaluationStatus',
          'submittedAt',
          'assignedAt',
        ],
      }
    }),

  /**
   * Export filtering results as CSV data
   */
  filteringResults: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      const results = await ctx.prisma.filteringResult.findMany({
        where: { roundId: input.roundId },
        include: {
          project: {
            select: {
              title: true,
              teamName: true,
              competitionCategory: true,
              country: true,
              oceanIssue: true,
              tags: true,
            },
          },
        },
        orderBy: { project: { title: 'asc' } },
      })

      // Collect all unique AI screening keys across all results
      const aiKeys = new Set<string>()
      results.forEach((r) => {
        if (r.aiScreeningJson && typeof r.aiScreeningJson === 'object') {
          const screening = r.aiScreeningJson as Record<string, Record<string, unknown>>
          for (const ruleResult of Object.values(screening)) {
            if (ruleResult && typeof ruleResult === 'object') {
              Object.keys(ruleResult).forEach((k) => aiKeys.add(k))
            }
          }
        }
      })

      const sortedAiKeys = Array.from(aiKeys).sort()

      const data = results.map((r) => {
        // Flatten AI screening - take first rule result's values
        const aiFlat: Record<string, unknown> = {}
        if (r.aiScreeningJson && typeof r.aiScreeningJson === 'object') {
          const screening = r.aiScreeningJson as Record<string, Record<string, unknown>>
          const firstEntry = Object.values(screening)[0]
          if (firstEntry && typeof firstEntry === 'object') {
            for (const key of sortedAiKeys) {
              const val = firstEntry[key]
              aiFlat[`ai_${key}`] = val !== undefined ? String(val) : ''
            }
          }
        }

        return {
          projectTitle: r.project.title,
          teamName: r.project.teamName ?? '',
          category: r.project.competitionCategory ?? '',
          country: r.project.country ?? '',
          oceanIssue: r.project.oceanIssue ?? '',
          tags: r.project.tags.join(', '),
          outcome: r.outcome,
          finalOutcome: r.finalOutcome ?? '',
          overrideReason: r.overrideReason ?? '',
          ...aiFlat,
        }
      })

      // Build columns list
      const baseColumns = [
        'projectTitle',
        'teamName',
        'category',
        'country',
        'oceanIssue',
        'tags',
        'outcome',
        'finalOutcome',
        'overrideReason',
      ]
      const aiColumns = sortedAiKeys.map((k) => `ai_${k}`)

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'EXPORT',
        entityType: 'FilteringResult',
        detailsJson: { roundId: input.roundId, count: data.length },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return {
        data,
        columns: [...baseColumns, ...aiColumns],
      }
    }),

  /**
   * Export audit logs as CSV data
   */
  auditLogs: adminProcedure
    .input(
      z.object({
        userId: z.string().optional(),
        action: z.string().optional(),
        entityType: z.string().optional(),
        startDate: z.date().optional(),
        endDate: z.date().optional(),
      })
    )
    .query(async ({ ctx, input }) => {
      const { userId, action, entityType, startDate, endDate } = input

      const where: Record<string, unknown> = {}

      if (userId) where.userId = userId
      if (action) where.action = { contains: action, mode: 'insensitive' }
      if (entityType) where.entityType = entityType
      if (startDate || endDate) {
        where.timestamp = {}
        if (startDate) (where.timestamp as Record<string, Date>).gte = startDate
        if (endDate) (where.timestamp as Record<string, Date>).lte = endDate
      }

      const logs = await ctx.prisma.auditLog.findMany({
        where,
        orderBy: { timestamp: 'desc' },
        include: {
          user: { select: { name: true, email: true } },
        },
        take: 10000, // Limit export to 10k records
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'EXPORT',
        entityType: 'AuditLog',
        detailsJson: {
          count: logs.length,
          exportType: 'auditLogs',
          filters: { userId, action, entityType, startDate: startDate?.toISOString(), endDate: endDate?.toISOString() },
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      const data = logs.map((log) => ({
        timestamp: log.timestamp.toISOString(),
        userName: log.user?.name ?? 'System',
        userEmail: log.user?.email ?? 'N/A',
        action: log.action,
        entityType: log.entityType,
        entityId: log.entityId ?? '',
        ipAddress: log.ipAddress ?? '',
        userAgent: log.userAgent ?? '',
        details: log.detailsJson ? JSON.stringify(log.detailsJson) : '',
      }))

      return {
        data,
        columns: [
          'timestamp',
          'userName',
          'userEmail',
          'action',
          'entityType',
          'entityId',
          'ipAddress',
          'userAgent',
          'details',
        ],
      }
    }),

  // =========================================================================
  // PDF Report Data (F10)
  // =========================================================================

  /**
   * Compile structured data for PDF report generation
   */
  getReportData: observerProcedure
    .input(
      z.object({
        roundId: z.string(),
        sections: z.array(z.string()).optional(),
      })
    )
    .query(async ({ ctx, input }) => {
      const includeSection = (name: string) =>
        !input.sections || input.sections.length === 0 || input.sections.includes(name)

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

      const result: Record<string, unknown> = {
        roundName: round.name,
        programName: round.competition.program.name,
        programYear: round.competition.program.year,
        generatedAt: new Date().toISOString(),
      }

      // Summary stats
      if (includeSection('summary')) {
        const [projectCount, assignmentCount, evaluationCount, jurorCount] = await Promise.all([
          ctx.prisma.project.count({
            where: { assignments: { some: { roundId: input.roundId } } },
          }),
          ctx.prisma.assignment.count({ where: { roundId: input.roundId } }),
          ctx.prisma.evaluation.count({
            where: {
              assignment: { roundId: input.roundId },
              status: 'SUBMITTED',
            },
          }),
          ctx.prisma.assignment.groupBy({
            by: ['userId'],
            where: { roundId: input.roundId },
          }),
        ])

        result.summary = {
          projectCount,
          assignmentCount,
          evaluationCount,
          jurorCount: jurorCount.length,
          completionRate: assignmentCount > 0
            ? Math.round((evaluationCount / assignmentCount) * 100)
            : 0,
        }
      }

      // Score distributions
      if (includeSection('scoreDistribution')) {
        const evaluations = await ctx.prisma.evaluation.findMany({
          where: {
            assignment: { roundId: input.roundId },
            status: 'SUBMITTED',
          },
          select: { globalScore: true },
        })

        const scores = evaluations
          .map((e) => e.globalScore)
          .filter((s): s is number => s !== null)

        result.scoreDistribution = {
          distribution: Array.from({ length: 10 }, (_, i) => ({
            score: i + 1,
            count: scores.filter((s) => Math.round(s) === i + 1).length,
          })),
          average: scores.length > 0
            ? scores.reduce((a, b) => a + b, 0) / scores.length
            : null,
          total: scores.length,
        }
      }

      // Rankings
      if (includeSection('rankings')) {
        const projects = await ctx.prisma.project.findMany({
          where: { assignments: { some: { roundId: input.roundId } } },
          select: {
            id: true,
            title: true,
            teamName: true,
            status: true,
            assignments: {
              select: {
                evaluation: {
                  select: { globalScore: true, binaryDecision: true, status: true },
                },
              },
            },
          },
        })

        const rankings = projects
          .map((p) => {
            const submitted = p.assignments
              .map((a) => a.evaluation)
              .filter((e) => e?.status === 'SUBMITTED')
            const scores = submitted
              .map((e) => e?.globalScore)
              .filter((s): s is number => s !== null)
            const yesVotes = submitted.filter((e) => e?.binaryDecision === true).length

            return {
              title: p.title,
              teamName: p.teamName,
              status: p.status,
              evaluationCount: submitted.length,
              averageScore: scores.length > 0
                ? scores.reduce((a, b) => a + b, 0) / scores.length
                : null,
              yesPercentage: submitted.length > 0
                ? (yesVotes / submitted.length) * 100
                : null,
            }
          })
          .filter((r) => r.averageScore !== null)
          .sort((a, b) => (b.averageScore || 0) - (a.averageScore || 0))

        result.rankings = rankings
      }

      // Juror stats
      if (includeSection('jurorStats')) {
        const assignments = await ctx.prisma.assignment.findMany({
          where: { roundId: input.roundId },
          include: {
            user: { select: { name: true, email: true } },
            evaluation: { select: { status: true, globalScore: true } },
          },
        })

        const byUser: Record<string, { name: string; assigned: number; completed: number; scores: number[] }> = {}
        assignments.forEach((a) => {
          if (!byUser[a.userId]) {
            byUser[a.userId] = {
              name: a.user.name || a.user.email || 'Unknown',
              assigned: 0,
              completed: 0,
              scores: [],
            }
          }
          byUser[a.userId].assigned++
          if (a.evaluation?.status === 'SUBMITTED') {
            byUser[a.userId].completed++
            if (a.evaluation.globalScore !== null) {
              byUser[a.userId].scores.push(a.evaluation.globalScore)
            }
          }
        })

        result.jurorStats = Object.values(byUser).map((u) => ({
          name: u.name,
          assigned: u.assigned,
          completed: u.completed,
          completionRate: u.assigned > 0 ? Math.round((u.completed / u.assigned) * 100) : 0,
          averageScore: u.scores.length > 0
            ? u.scores.reduce((a, b) => a + b, 0) / u.scores.length
            : null,
        }))
      }

      // Criteria breakdown
      if (includeSection('criteriaBreakdown')) {
        // Fetch all active forms (shared + category-specific) and merge criteria
        const allForms = await ctx.prisma.evaluationForm.findMany({
          where: { roundId: input.roundId, isActive: true },
        })

        const seenIds = new Set<string>()
        const allCriteria: Array<{ id: string; label: string }> = []
        for (const f of allForms) {
          const fc = (f.criteriaJson as Array<{ id: string; label: string }> | null) ?? []
          for (const c of fc) {
            if (!seenIds.has(c.id)) {
              seenIds.add(c.id)
              allCriteria.push(c)
            }
          }
        }

        if (allCriteria.length > 0) {
          const evaluations = await ctx.prisma.evaluation.findMany({
            where: {
              assignment: { roundId: input.roundId },
              status: 'SUBMITTED',
            },
            select: { criterionScoresJson: true },
          })

          result.criteriaBreakdown = allCriteria.map((c) => {
            const scores: number[] = []
            evaluations.forEach((e) => {
              const cs = e.criterionScoresJson as Record<string, number> | null
              if (cs && typeof cs[c.id] === 'number') {
                scores.push(cs[c.id])
              }
            })

            return {
              id: c.id,
              label: c.label,
              averageScore: scores.length > 0
                ? scores.reduce((a, b) => a + b, 0) / scores.length
                : null,
              count: scores.length,
            }
          })
        }
      }

      // Audit log for report generation
      try {
        await logAudit({
          prisma: ctx.prisma,
          userId: ctx.user.id,
          action: 'REPORT_GENERATED',
          entityType: 'Round',
          entityId: input.roundId,
          detailsJson: { sections: input.sections },
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        })
      } catch (err) {
        console.error('Failed to write audit log for round export:', err)
        // Never throw on audit failure
      }

      return result
    }),
})
