import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { Prisma, FilteringOutcome, ProjectRoundStateValue } from '@prisma/client'
import { router, protectedProcedure, adminProcedure } from '../trpc'
import { logAudit } from '@/server/utils/audit'

export const decisionRouter = router({
  /**
   * Override a project's stage state or filtering result
   */
  override: adminProcedure
    .input(
      z.object({
        entityType: z.enum([
          'ProjectRoundState',
          'FilteringResult',
          'AwardEligibility',
        ]),
        entityId: z.string(),
        newValue: z.record(z.unknown()),
        reasonCode: z.enum([
          'DATA_CORRECTION',
          'POLICY_EXCEPTION',
          'JURY_CONFLICT',
          'SPONSOR_DECISION',
          'ADMIN_DISCRETION',
        ]),
        reasonText: z.string().max(2000).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      let previousValue: Record<string, unknown> = {}

      // Fetch current value based on entity type
      switch (input.entityType) {
        case 'ProjectRoundState': {
          const prs = await ctx.prisma.projectRoundState.findUniqueOrThrow({
            where: { id: input.entityId },
          })
          previousValue = {
            state: prs.state,
            metadataJson: prs.metadataJson,
          }

          // Validate the new state
          const newState = input.newValue.state as string | undefined
          if (
            newState &&
            !['PENDING', 'IN_PROGRESS', 'PASSED', 'REJECTED', 'ROUTED', 'COMPLETED', 'WITHDRAWN'].includes(newState)
          ) {
            throw new TRPCError({
              code: 'BAD_REQUEST',
              message: `Invalid state: ${newState}`,
            })
          }

          await ctx.prisma.$transaction(async (tx) => {
            await tx.projectRoundState.update({
              where: { id: input.entityId },
              data: {
                state: newState ? (newState as ProjectRoundStateValue) : prs.state,
                metadataJson: {
                  ...(prs.metadataJson as Record<string, unknown> ?? {}),
                  lastOverride: {
                    by: ctx.user.id,
                    at: new Date().toISOString(),
                    reason: input.reasonCode,
                  },
                } as Prisma.InputJsonValue,
              },
            })

            await tx.overrideAction.create({
              data: {
                entityType: input.entityType,
                entityId: input.entityId,
                previousValue: previousValue as Prisma.InputJsonValue,
                newValueJson: input.newValue as Prisma.InputJsonValue,
                reasonCode: input.reasonCode,
                reasonText: input.reasonText ?? null,
                actorId: ctx.user.id,
              },
            })

            await tx.decisionAuditLog.create({
              data: {
                eventType: 'override.applied',
                entityType: input.entityType,
                entityId: input.entityId,
                actorId: ctx.user.id,
                detailsJson: {
                  previousValue,
                  newValue: input.newValue,
                  reasonCode: input.reasonCode,
                  reasonText: input.reasonText,
                } as Prisma.InputJsonValue,
                snapshotJson: previousValue as Prisma.InputJsonValue,
              },
            })
          })

          // Audit outside transaction so failures don't roll back the override
          await logAudit({
            prisma: ctx.prisma,
            userId: ctx.user.id,
            action: 'DECISION_OVERRIDE',
            entityType: input.entityType,
            entityId: input.entityId,
            detailsJson: {
              reasonCode: input.reasonCode,
              reasonText: input.reasonText,
              previousState: previousValue.state,
              newState: input.newValue.state,
            },
            ipAddress: ctx.ip,
            userAgent: ctx.userAgent,
          })
          break
        }

        case 'FilteringResult': {
          const fr = await ctx.prisma.filteringResult.findUniqueOrThrow({
            where: { id: input.entityId },
          })
          previousValue = {
            outcome: fr.outcome,
            aiScreeningJson: fr.aiScreeningJson,
          }

          const newOutcome = input.newValue.outcome as string | undefined

          await ctx.prisma.$transaction(async (tx) => {
            if (newOutcome) {
              await tx.filteringResult.update({
                where: { id: input.entityId },
                data: { finalOutcome: newOutcome as FilteringOutcome },
              })
            }

            await tx.overrideAction.create({
              data: {
                entityType: input.entityType,
                entityId: input.entityId,
                previousValue: previousValue as Prisma.InputJsonValue,
                newValueJson: input.newValue as Prisma.InputJsonValue,
                reasonCode: input.reasonCode,
                reasonText: input.reasonText ?? null,
                actorId: ctx.user.id,
              },
            })

            await tx.decisionAuditLog.create({
              data: {
                eventType: 'override.applied',
                entityType: input.entityType,
                entityId: input.entityId,
                actorId: ctx.user.id,
                detailsJson: {
                  previousValue,
                  newValue: input.newValue,
                  reasonCode: input.reasonCode,
                } as Prisma.InputJsonValue,
              },
            })
          })

          // Audit outside transaction so failures don't roll back the override
          await logAudit({
            prisma: ctx.prisma,
            userId: ctx.user.id,
            action: 'DECISION_OVERRIDE',
            entityType: input.entityType,
            entityId: input.entityId,
            detailsJson: {
              reasonCode: input.reasonCode,
              previousOutcome: (previousValue as Record<string, unknown>).outcome,
              newOutcome,
            },
            ipAddress: ctx.ip,
            userAgent: ctx.userAgent,
          })
          break
        }

        case 'AwardEligibility': {
          const ae = await ctx.prisma.awardEligibility.findUniqueOrThrow({
            where: { id: input.entityId },
          })
          previousValue = {
            eligible: ae.eligible,
            method: ae.method,
          }

          const newEligible = input.newValue.eligible as boolean | undefined

          await ctx.prisma.$transaction(async (tx) => {
            if (newEligible !== undefined) {
              await tx.awardEligibility.update({
                where: { id: input.entityId },
                data: {
                  eligible: newEligible,
                  method: 'MANUAL',
                  overriddenBy: ctx.user.id,
                  overriddenAt: new Date(),
                },
              })
            }

            await tx.overrideAction.create({
              data: {
                entityType: input.entityType,
                entityId: input.entityId,
                previousValue: previousValue as Prisma.InputJsonValue,
                newValueJson: input.newValue as Prisma.InputJsonValue,
                reasonCode: input.reasonCode,
                reasonText: input.reasonText ?? null,
                actorId: ctx.user.id,
              },
            })

            await tx.decisionAuditLog.create({
              data: {
                eventType: 'override.applied',
                entityType: input.entityType,
                entityId: input.entityId,
                actorId: ctx.user.id,
                detailsJson: {
                  previousValue,
                  newValue: input.newValue,
                  reasonCode: input.reasonCode,
                } as Prisma.InputJsonValue,
              },
            })
          })

          // Audit outside transaction so failures don't roll back the override
          await logAudit({
            prisma: ctx.prisma,
            userId: ctx.user.id,
            action: 'DECISION_OVERRIDE',
            entityType: input.entityType,
            entityId: input.entityId,
            detailsJson: {
              reasonCode: input.reasonCode,
              previousEligible: previousValue.eligible,
              newEligible,
            },
            ipAddress: ctx.ip,
            userAgent: ctx.userAgent,
          })
          break
        }
      }

      return { success: true, entityType: input.entityType, entityId: input.entityId }
    }),

  /**
   * Get the full decision audit timeline for an entity
   */
  auditTimeline: protectedProcedure
    .input(
      z.object({
        entityType: z.string(),
        entityId: z.string(),
      })
    )
    .query(async ({ ctx, input }) => {
      const [decisionLogs, overrideActions] = await Promise.all([
        ctx.prisma.decisionAuditLog.findMany({
          where: {
            entityType: input.entityType,
            entityId: input.entityId,
          },
          orderBy: { createdAt: 'desc' },
        }),
        ctx.prisma.overrideAction.findMany({
          where: {
            entityType: input.entityType,
            entityId: input.entityId,
          },
          orderBy: { createdAt: 'desc' },
        }),
      ])

      // Merge and sort by timestamp
      const timeline = [
        ...decisionLogs.map((dl) => ({
          type: 'decision' as const,
          id: dl.id,
          eventType: dl.eventType,
          actorId: dl.actorId,
          details: dl.detailsJson,
          snapshot: dl.snapshotJson,
          createdAt: dl.createdAt,
        })),
        ...overrideActions.map((oa) => ({
          type: 'override' as const,
          id: oa.id,
          eventType: `override.${oa.reasonCode}`,
          actorId: oa.actorId,
          details: {
            previousValue: oa.previousValue,
            newValue: oa.newValueJson,
            reasonCode: oa.reasonCode,
            reasonText: oa.reasonText,
          },
          snapshot: null,
          createdAt: oa.createdAt,
        })),
      ].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())

      return { entityType: input.entityType, entityId: input.entityId, timeline }
    }),

  /**
   * Get override actions (paginated, admin only)
   */
  getOverrides: adminProcedure
    .input(
      z.object({
        entityType: z.string().optional(),
        reasonCode: z
          .enum([
            'DATA_CORRECTION',
            'POLICY_EXCEPTION',
            'JURY_CONFLICT',
            'SPONSOR_DECISION',
            'ADMIN_DISCRETION',
          ])
          .optional(),
        cursor: z.string().optional(),
        limit: z.number().int().min(1).max(100).default(50),
      })
    )
    .query(async ({ ctx, input }) => {
      const where: Prisma.OverrideActionWhereInput = {}
      if (input.entityType) where.entityType = input.entityType
      if (input.reasonCode) where.reasonCode = input.reasonCode

      const items = await ctx.prisma.overrideAction.findMany({
        where,
        take: input.limit + 1,
        cursor: input.cursor ? { id: input.cursor } : undefined,
        orderBy: { createdAt: 'desc' },
      })

      let nextCursor: string | undefined
      if (items.length > input.limit) {
        const next = items.pop()
        nextCursor = next?.id
      }

      return { items, nextCursor }
    }),
})
