import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { Prisma, PrismaClient } from '@prisma/client'
import { router, adminProcedure, protectedProcedure } from '../trpc'
import { executeFilteringRules, type ProgressCallback } from '../services/ai-filtering'
import { logAudit } from '../utils/audit'
import { isOpenAIConfigured, testOpenAIConnection } from '@/lib/openai'
import { prisma } from '@/lib/prisma'
import {
  notifyAdmins,
  NotificationTypes,
} from '../services/in-app-notification'

// Background job execution function
async function runFilteringJob(jobId: string, roundId: string, userId: string) {
  try {
    // Update job to running
    await prisma.filteringJob.update({
      where: { id: jobId },
      data: { status: 'RUNNING', startedAt: new Date() },
    })

    // Get rules
    const rules = await prisma.filteringRule.findMany({
      where: { roundId },
      orderBy: { priority: 'asc' },
    })

    // Get projects
    const projects = await prisma.project.findMany({
      where: { roundId },
      include: {
        files: {
          select: { id: true, fileName: true, fileType: true },
        },
      },
    })

    // Calculate batch info
    const BATCH_SIZE = 20
    const totalBatches = Math.ceil(projects.length / BATCH_SIZE)

    await prisma.filteringJob.update({
      where: { id: jobId },
      data: { totalProjects: projects.length, totalBatches },
    })

    // Progress callback
    const onProgress: ProgressCallback = async (progress) => {
      await prisma.filteringJob.update({
        where: { id: jobId },
        data: {
          currentBatch: progress.currentBatch,
          processedCount: progress.processedCount,
        },
      })
    }

    // Execute rules
    const results = await executeFilteringRules(rules, projects, userId, roundId, onProgress)

    // Count outcomes
    const passedCount = results.filter((r) => r.outcome === 'PASSED').length
    const filteredCount = results.filter((r) => r.outcome === 'FILTERED_OUT').length
    const flaggedCount = results.filter((r) => r.outcome === 'FLAGGED').length

    // Upsert results
    await prisma.$transaction(
      results.map((r) =>
        prisma.filteringResult.upsert({
          where: {
            roundId_projectId: {
              roundId,
              projectId: r.projectId,
            },
          },
          create: {
            roundId,
            projectId: r.projectId,
            outcome: r.outcome,
            ruleResultsJson: r.ruleResults as unknown as Prisma.InputJsonValue,
            aiScreeningJson: (r.aiScreeningJson ?? Prisma.JsonNull) as Prisma.InputJsonValue,
          },
          update: {
            outcome: r.outcome,
            ruleResultsJson: r.ruleResults as unknown as Prisma.InputJsonValue,
            aiScreeningJson: (r.aiScreeningJson ?? Prisma.JsonNull) as Prisma.InputJsonValue,
            overriddenBy: null,
            overriddenAt: null,
            overrideReason: null,
            finalOutcome: null,
          },
        })
      )
    )

    // Mark job as completed
    await prisma.filteringJob.update({
      where: { id: jobId },
      data: {
        status: 'COMPLETED',
        completedAt: new Date(),
        processedCount: projects.length,
        passedCount,
        filteredCount,
        flaggedCount,
      },
    })

    // Audit log
    await logAudit({
      userId,
      action: 'UPDATE',
      entityType: 'Round',
      entityId: roundId,
      detailsJson: {
        action: 'EXECUTE_FILTERING',
        jobId,
        projectCount: projects.length,
        passed: passedCount,
        filteredOut: filteredCount,
        flagged: flaggedCount,
      },
    })

    // Get round name for notification
    const round = await prisma.round.findUnique({
      where: { id: roundId },
      select: { name: true },
    })

    // Notify admins that filtering is complete
    await notifyAdmins({
      type: NotificationTypes.FILTERING_COMPLETE,
      title: 'AI Filtering Complete',
      message: `Filtering complete for ${round?.name || 'round'}: ${passedCount} passed, ${flaggedCount} flagged, ${filteredCount} filtered out`,
      linkUrl: `/admin/rounds/${roundId}/filtering/results`,
      linkLabel: 'View Results',
      priority: 'high',
      metadata: {
        roundId,
        jobId,
        projectCount: projects.length,
        passedCount,
        filteredCount,
        flaggedCount,
      },
    })
  } catch (error) {
    console.error('[Filtering Job] Error:', error)
    await prisma.filteringJob.update({
      where: { id: jobId },
      data: {
        status: 'FAILED',
        completedAt: new Date(),
        errorMessage: error instanceof Error ? error.message : 'Unknown error',
      },
    })

    // Notify admins of failure
    await notifyAdmins({
      type: NotificationTypes.FILTERING_FAILED,
      title: 'AI Filtering Failed',
      message: `Filtering job failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
      linkUrl: `/admin/rounds/${roundId}/filtering`,
      linkLabel: 'View Details',
      priority: 'urgent',
      metadata: { roundId, jobId, error: error instanceof Error ? error.message : 'Unknown error' },
    })
  }
}

export const filteringRouter = router({
  /**
   * Check if AI is configured and ready for filtering
   */
  checkAIStatus: protectedProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      // Check if round has AI rules
      const aiRules = await ctx.prisma.filteringRule.count({
        where: {
          roundId: input.roundId,
          ruleType: 'AI_SCREENING',
          isActive: true,
        },
      })

      if (aiRules === 0) {
        return { hasAIRules: false, configured: true, error: null }
      }

      // Check if OpenAI is configured
      const configured = await isOpenAIConfigured()
      if (!configured) {
        return {
          hasAIRules: true,
          configured: false,
          error: 'OpenAI API key not configured',
        }
      }

      // Test the connection
      const testResult = await testOpenAIConnection()
      return {
        hasAIRules: true,
        configured: testResult.success,
        error: testResult.error || null,
        model: testResult.modelTested,
      }
    }),

  /**
   * Get filtering rules for a round
   */
  getRules: protectedProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.filteringRule.findMany({
        where: { roundId: input.roundId },
        orderBy: { priority: 'asc' },
      })
    }),

  /**
   * Create a filtering rule
   */
  createRule: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        name: z.string().min(1),
        ruleType: z.enum(['FIELD_BASED', 'DOCUMENT_CHECK', 'AI_SCREENING']),
        configJson: z.record(z.unknown()),
        priority: z.number().int().default(0),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const rule = await ctx.prisma.filteringRule.create({
        data: {
          roundId: input.roundId,
          name: input.name,
          ruleType: input.ruleType,
          configJson: input.configJson as Prisma.InputJsonValue,
          priority: input.priority,
        },
      })

      await logAudit({
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'FilteringRule',
        entityId: rule.id,
        detailsJson: { roundId: input.roundId, name: input.name, ruleType: input.ruleType },
      })

      return rule
    }),

  /**
   * Update a filtering rule
   */
  updateRule: adminProcedure
    .input(
      z.object({
        id: z.string(),
        name: z.string().min(1).optional(),
        configJson: z.record(z.unknown()).optional(),
        priority: z.number().int().optional(),
        isActive: z.boolean().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id, configJson, ...rest } = input
      const rule = await ctx.prisma.filteringRule.update({
        where: { id },
        data: {
          ...rest,
          ...(configJson !== undefined && { configJson: configJson as Prisma.InputJsonValue }),
        },
      })

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

      return rule
    }),

  /**
   * Delete a filtering rule
   */
  deleteRule: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      await ctx.prisma.filteringRule.delete({ where: { id: input.id } })

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

  /**
   * Reorder rules (batch update priorities)
   */
  reorderRules: adminProcedure
    .input(
      z.object({
        rules: z.array(
          z.object({
            id: z.string(),
            priority: z.number().int(),
          })
        ),
      })
    )
    .mutation(async ({ ctx, input }) => {
      await ctx.prisma.$transaction(
        input.rules.map((r) =>
          ctx.prisma.filteringRule.update({
            where: { id: r.id },
            data: { priority: r.priority },
          })
        )
      )
    }),

  /**
   * Start a filtering job (runs in background)
   */
  startJob: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      // Check if there's already a running job
      const existingJob = await ctx.prisma.filteringJob.findFirst({
        where: { roundId: input.roundId, status: 'RUNNING' },
      })
      if (existingJob) {
        throw new TRPCError({
          code: 'CONFLICT',
          message: 'A filtering job is already running for this round',
        })
      }

      // Get rules
      const rules = await ctx.prisma.filteringRule.findMany({
        where: { roundId: input.roundId },
        orderBy: { priority: 'asc' },
      })

      if (rules.length === 0) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'No filtering rules configured for this round',
        })
      }

      // Check AI config if needed
      const hasAIRules = rules.some((r) => r.ruleType === 'AI_SCREENING' && r.isActive)
      if (hasAIRules) {
        const aiConfigured = await isOpenAIConfigured()
        if (!aiConfigured) {
          throw new TRPCError({
            code: 'PRECONDITION_FAILED',
            message:
              'AI screening rules require OpenAI to be configured. Go to Settings → AI to configure your API key.',
          })
        }
        const testResult = await testOpenAIConnection()
        if (!testResult.success) {
          throw new TRPCError({
            code: 'PRECONDITION_FAILED',
            message: `AI configuration error: ${testResult.error}. Go to Settings → AI to fix.`,
          })
        }
      }

      // Count projects
      const projectCount = await ctx.prisma.project.count({
        where: { roundId: input.roundId },
      })
      if (projectCount === 0) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'No projects found in this round',
        })
      }

      // Create job
      const job = await ctx.prisma.filteringJob.create({
        data: {
          roundId: input.roundId,
          status: 'PENDING',
          totalProjects: projectCount,
        },
      })

      // Start background execution (non-blocking)
      setImmediate(() => {
        runFilteringJob(job.id, input.roundId, ctx.user.id).catch(console.error)
      })

      return { jobId: job.id, message: 'Filtering job started' }
    }),

  /**
   * Get current job status
   */
  getJobStatus: protectedProcedure
    .input(z.object({ jobId: z.string() }))
    .query(async ({ ctx, input }) => {
      const job = await ctx.prisma.filteringJob.findUnique({
        where: { id: input.jobId },
      })
      if (!job) {
        throw new TRPCError({ code: 'NOT_FOUND', message: 'Job not found' })
      }
      return job
    }),

  /**
   * Get latest job for a round
   */
  getLatestJob: protectedProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.filteringJob.findFirst({
        where: { roundId: input.roundId },
        orderBy: { createdAt: 'desc' },
      })
    }),

  /**
   * Execute all filtering rules against projects in a round (synchronous, legacy)
   */
  executeRules: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      // Get rules
      const rules = await ctx.prisma.filteringRule.findMany({
        where: { roundId: input.roundId },
        orderBy: { priority: 'asc' },
      })

      if (rules.length === 0) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'No filtering rules configured for this round',
        })
      }

      // Check if any AI_SCREENING rules exist
      const hasAIRules = rules.some((r) => r.ruleType === 'AI_SCREENING' && r.isActive)

      if (hasAIRules) {
        // Verify OpenAI is configured before proceeding
        const aiConfigured = await isOpenAIConfigured()
        if (!aiConfigured) {
          throw new TRPCError({
            code: 'PRECONDITION_FAILED',
            message:
              'AI screening rules require OpenAI to be configured. Go to Settings → AI to configure your API key.',
          })
        }

        // Also verify the model works
        const testResult = await testOpenAIConnection()
        if (!testResult.success) {
          throw new TRPCError({
            code: 'PRECONDITION_FAILED',
            message: `AI configuration error: ${testResult.error}. Go to Settings → AI to fix.`,
          })
        }
      }

      // Get projects in this round
      const projects = await ctx.prisma.project.findMany({
        where: { roundId: input.roundId },
        include: {
          files: {
            select: { id: true, fileName: true, fileType: true },
          },
        },
      })

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

      // Execute rules
      const results = await executeFilteringRules(rules, projects)

      // Upsert results
      await ctx.prisma.$transaction(
        results.map((r) =>
          ctx.prisma.filteringResult.upsert({
            where: {
              roundId_projectId: {
                roundId: input.roundId,
                projectId: r.projectId,
              },
            },
            create: {
              roundId: input.roundId,
              projectId: r.projectId,
              outcome: r.outcome,
              ruleResultsJson: r.ruleResults as unknown as Prisma.InputJsonValue,
              aiScreeningJson: (r.aiScreeningJson ?? Prisma.JsonNull) as Prisma.InputJsonValue,
            },
            update: {
              outcome: r.outcome,
              ruleResultsJson: r.ruleResults as unknown as Prisma.InputJsonValue,
              aiScreeningJson: (r.aiScreeningJson ?? Prisma.JsonNull) as Prisma.InputJsonValue,
              // Clear any previous override
              overriddenBy: null,
              overriddenAt: null,
              overrideReason: null,
              finalOutcome: null,
            },
          })
        )
      )

      await logAudit({
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'Round',
        entityId: input.roundId,
        detailsJson: {
          action: 'EXECUTE_FILTERING',
          projectCount: projects.length,
          passed: results.filter((r) => r.outcome === 'PASSED').length,
          filteredOut: results.filter((r) => r.outcome === 'FILTERED_OUT').length,
          flagged: results.filter((r) => r.outcome === 'FLAGGED').length,
        },
      })

      return {
        total: results.length,
        passed: results.filter((r) => r.outcome === 'PASSED').length,
        filteredOut: results.filter((r) => r.outcome === 'FILTERED_OUT').length,
        flagged: results.filter((r) => r.outcome === 'FLAGGED').length,
      }
    }),

  /**
   * Get filtering results for a round (paginated)
   */
  getResults: protectedProcedure
    .input(
      z.object({
        roundId: z.string(),
        outcome: z.enum(['PASSED', 'FILTERED_OUT', 'FLAGGED']).optional(),
        page: z.number().int().min(1).default(1),
        perPage: z.number().int().min(1).max(100).default(20),
      })
    )
    .query(async ({ ctx, input }) => {
      const { roundId, outcome, page, perPage } = input
      const skip = (page - 1) * perPage

      const where: Record<string, unknown> = { roundId }
      if (outcome) where.outcome = outcome

      const [results, total] = await Promise.all([
        ctx.prisma.filteringResult.findMany({
          where,
          skip,
          take: perPage,
          orderBy: { createdAt: 'desc' },
          include: {
            project: {
              select: {
                id: true,
                title: true,
                teamName: true,
                competitionCategory: true,
                country: true,
              },
            },
            overriddenByUser: {
              select: { id: true, name: true, email: true },
            },
          },
        }),
        ctx.prisma.filteringResult.count({ where }),
      ])

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

  /**
   * Get aggregate stats for filtering results
   */
  getResultStats: protectedProcedure
    .input(z.object({ roundId: z.string() }))
    .query(async ({ ctx, input }) => {
      const [passed, filteredOut, flagged, overridden] = await Promise.all([
        ctx.prisma.filteringResult.count({
          where: { roundId: input.roundId, outcome: 'PASSED' },
        }),
        ctx.prisma.filteringResult.count({
          where: { roundId: input.roundId, outcome: 'FILTERED_OUT' },
        }),
        ctx.prisma.filteringResult.count({
          where: { roundId: input.roundId, outcome: 'FLAGGED' },
        }),
        ctx.prisma.filteringResult.count({
          where: { roundId: input.roundId, overriddenBy: { not: null } },
        }),
      ])

      return { passed, filteredOut, flagged, overridden, total: passed + filteredOut + flagged }
    }),

  /**
   * Override a single filtering result
   */
  overrideResult: adminProcedure
    .input(
      z.object({
        id: z.string(),
        finalOutcome: z.enum(['PASSED', 'FILTERED_OUT', 'FLAGGED']),
        reason: z.string().min(1),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const result = await ctx.prisma.filteringResult.update({
        where: { id: input.id },
        data: {
          finalOutcome: input.finalOutcome,
          overriddenBy: ctx.user.id,
          overriddenAt: new Date(),
          overrideReason: input.reason,
        },
      })

      await logAudit({
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'FilteringResult',
        entityId: input.id,
        detailsJson: {
          action: 'OVERRIDE',
          originalOutcome: result.outcome,
          finalOutcome: input.finalOutcome,
          reason: input.reason,
        },
      })

      return result
    }),

  /**
   * Bulk override multiple results
   */
  bulkOverride: adminProcedure
    .input(
      z.object({
        ids: z.array(z.string()),
        finalOutcome: z.enum(['PASSED', 'FILTERED_OUT', 'FLAGGED']),
        reason: z.string().min(1),
      })
    )
    .mutation(async ({ ctx, input }) => {
      await ctx.prisma.filteringResult.updateMany({
        where: { id: { in: input.ids } },
        data: {
          finalOutcome: input.finalOutcome,
          overriddenBy: ctx.user.id,
          overriddenAt: new Date(),
          overrideReason: input.reason,
        },
      })

      await logAudit({
        userId: ctx.user.id,
        action: 'BULK_UPDATE_STATUS',
        entityType: 'FilteringResult',
        detailsJson: {
          action: 'BULK_OVERRIDE',
          count: input.ids.length,
          finalOutcome: input.finalOutcome,
        },
      })

      return { updated: input.ids.length }
    }),

  /**
   * Finalize filtering results — apply outcomes to project statuses
   * PASSED → mark as ELIGIBLE and advance to next round
   * FILTERED_OUT → mark as REJECTED (data preserved)
   */
  finalizeResults: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      // Get current round to find the next one
      const currentRound = await ctx.prisma.round.findUniqueOrThrow({
        where: { id: input.roundId },
        select: { id: true, programId: true, sortOrder: true, name: true },
      })

      // Find the next round by sortOrder
      const nextRound = await ctx.prisma.round.findFirst({
        where: {
          programId: currentRound.programId,
          sortOrder: { gt: currentRound.sortOrder },
        },
        orderBy: { sortOrder: 'asc' },
        select: { id: true, name: true },
      })

      const results = await ctx.prisma.filteringResult.findMany({
        where: { roundId: input.roundId },
      })

      // Use finalOutcome if overridden, otherwise use outcome
      const filteredOutIds = results
        .filter((r) => (r.finalOutcome || r.outcome) === 'FILTERED_OUT')
        .map((r) => r.projectId)

      const passedIds = results
        .filter((r) => (r.finalOutcome || r.outcome) === 'PASSED')
        .map((r) => r.projectId)

      // Build transaction operations
      const operations: Prisma.PrismaPromise<unknown>[] = []

      // Filtered out projects get REJECTED status (data preserved)
      if (filteredOutIds.length > 0) {
        operations.push(
          ctx.prisma.project.updateMany({
            where: { roundId: input.roundId, id: { in: filteredOutIds } },
            data: { status: 'REJECTED' },
          })
        )
      }

      // Passed projects get ELIGIBLE status (or advance to next round)
      if (passedIds.length > 0) {
        if (nextRound) {
          // Advance passed projects to next round
          operations.push(
            ctx.prisma.project.updateMany({
              where: { roundId: input.roundId, id: { in: passedIds } },
              data: { roundId: nextRound.id, status: 'SUBMITTED' },
            })
          )
        } else {
          // No next round, just mark as eligible
          operations.push(
            ctx.prisma.project.updateMany({
              where: { roundId: input.roundId, id: { in: passedIds } },
              data: { status: 'ELIGIBLE' },
            })
          )
        }
      }

      // Execute all operations in a transaction
      await ctx.prisma.$transaction(operations)

      await logAudit({
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'Round',
        entityId: input.roundId,
        detailsJson: {
          action: 'FINALIZE_FILTERING',
          passed: passedIds.length,
          filteredOut: filteredOutIds.length,
          advancedToRound: nextRound?.name || null,
        },
      })

      return {
        passed: passedIds.length,
        filteredOut: filteredOutIds.length,
        advancedToRoundId: nextRound?.id || null,
        advancedToRoundName: nextRound?.name || null,
      }
    }),

  /**
   * Reinstate a filtered-out project back into the active pool
   */
  reinstateProject: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        projectId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Update filtering result
      await ctx.prisma.filteringResult.update({
        where: {
          roundId_projectId: {
            roundId: input.roundId,
            projectId: input.projectId,
          },
        },
        data: {
          finalOutcome: 'PASSED',
          overriddenBy: ctx.user.id,
          overriddenAt: new Date(),
          overrideReason: 'Reinstated by admin',
        },
      })

      // Restore project status
      await ctx.prisma.project.updateMany({
        where: { roundId: input.roundId, id: input.projectId },
        data: { status: 'ELIGIBLE' },
      })

      await logAudit({
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'FilteringResult',
        detailsJson: {
          action: 'REINSTATE',
          roundId: input.roundId,
          projectId: input.projectId,
        },
      })
    }),

  /**
   * Bulk reinstate filtered-out projects
   */
  bulkReinstate: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        projectIds: z.array(z.string()),
      })
    )
    .mutation(async ({ ctx, input }) => {
      await ctx.prisma.$transaction([
        ...input.projectIds.map((projectId) =>
          ctx.prisma.filteringResult.update({
            where: {
              roundId_projectId: {
                roundId: input.roundId,
                projectId,
              },
            },
            data: {
              finalOutcome: 'PASSED',
              overriddenBy: ctx.user.id,
              overriddenAt: new Date(),
              overrideReason: 'Bulk reinstated by admin',
            },
          })
        ),
        ctx.prisma.project.updateMany({
          where: { roundId: input.roundId, id: { in: input.projectIds } },
          data: { status: 'ELIGIBLE' },
        }),
      ])

      await logAudit({
        userId: ctx.user.id,
        action: 'BULK_UPDATE_STATUS',
        entityType: 'FilteringResult',
        detailsJson: {
          action: 'BULK_REINSTATE',
          roundId: input.roundId,
          count: input.projectIds.length,
        },
      })

      return { reinstated: input.projectIds.length }
    }),
})
