import { z } from 'zod'
import type { Prisma } from '@prisma/client'
import { router, protectedProcedure, adminProcedure } from '../trpc'
import { logAudit } from '../utils/audit'
import { wizardConfigSchema } from '@/types/wizard-config'
import { parseWizardConfig } from '@/lib/wizard-config'

export const programRouter = router({
  /**
   * List all programs with optional filtering.
   * When includeStages is true, returns stages nested under
   * pipelines -> tracks -> stages, flattened as `stages` for convenience.
   */
  list: protectedProcedure
    .input(
      z.object({
        status: z.enum(['DRAFT', 'ACTIVE', 'ARCHIVED']).optional(),
        includeStages: z.boolean().optional(),
      }).optional()
    )
    .query(async ({ ctx, input }) => {
      const includeStages = input?.includeStages || false

      const programs = await ctx.prisma.program.findMany({
        where: input?.status ? { status: input.status } : undefined,
        orderBy: { year: 'desc' },
        include: includeStages
          ? {
              competitions: {
                include: {
                  rounds: {
                    orderBy: { sortOrder: 'asc' },
                    include: {
                      _count: {
                        select: { assignments: true, projectRoundStates: true },
                      },
                      assignments: {
                        where: { evaluation: { status: 'SUBMITTED' } },
                        select: { id: true },
                      },
                    },
                  },
                },
              },
            }
          : undefined,
      })

      // Return programs with rounds flattened, preserving competitionId
      return programs.map((p) => {
        const allRounds = (p as any).competitions?.flatMap((c: any) =>
          (c.rounds || []).map((round: any) => ({ ...round, competitionId: c.id }))
        ) || []
        return {
          ...p,
          // Provide `stages` as alias for backward compatibility
          stages: allRounds.map((round: any) => ({
            ...round,
            assignments: undefined, // don't leak raw assignments array
            _count: {
              projects: round._count?.projectRoundStates || 0,
              assignments: round._count?.assignments || 0,
              evaluations: round.assignments?.length || 0,
            },
          })),
          // Main rounds array
          rounds: allRounds.map((round: any) => ({
            id: round.id,
            name: round.name,
            competitionId: round.competitionId,
            status: round.status,
            roundType: round.roundType,
            sortOrder: round.sortOrder,
            votingEndAt: round.windowCloseAt,
            _count: {
              projects: round._count?.projectRoundStates || 0,
              assignments: round._count?.assignments || 0,
              evaluations: round.assignments?.length || 0,
            },
          })),
        }
      })
    }),

  /**
   * Get a single program with its stages (via pipelines)
   */
  get: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const program = await ctx.prisma.program.findUniqueOrThrow({
        where: { id: input.id },
        include: {
          competitions: {
            include: {
              rounds: {
                orderBy: { sortOrder: 'asc' },
                include: {
                  _count: {
                    select: { assignments: true, projectRoundStates: true },
                  },
                },
              },
            },
          },
        },
      })

      // Flatten rounds from all competitions, preserving competitionId
      const allRounds = (program as any).competitions?.flatMap((c: any) =>
        (c.rounds || []).map((round: any) => ({ ...round, competitionId: c.id }))
      ) || []
      const rounds = allRounds.map((round: any) => ({
        ...round,
        _count: {
          projects: round._count?.projectRoundStates || 0,
          assignments: round._count?.assignments || 0,
        },
      })) || []

      return {
        ...program,
        // stages as alias for backward compatibility
        stages: rounds,
        rounds,
      }
    }),

  /**
   * Create a new program (admin only)
   */
  create: adminProcedure
    .input(
      z.object({
        name: z.string().min(1).max(255),
        year: z.number().int().min(2020).max(2100),
        description: z.string().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const program = await ctx.prisma.program.create({
        data: input,
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'CREATE',
        entityType: 'Program',
        entityId: program.id,
        detailsJson: input,
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return program
    }),

  /**
   * Update a program (admin only)
   */
  update: adminProcedure
    .input(
      z.object({
        id: z.string(),
        name: z.string().min(1).max(255).optional(),
        slug: z.string().min(1).max(100).optional(),
        status: z.enum(['DRAFT', 'ACTIVE', 'ARCHIVED']).optional(),
        description: z.string().optional(),
        settingsJson: z.record(z.any()).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id, ...data } = input

      const program = await ctx.prisma.program.update({
        where: { id },
        data,
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'Program',
        entityId: id,
        detailsJson: data,
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return program
    }),

  /**
   * Delete a program (admin only)
   * Note: This will cascade delete all rounds, projects, etc.
   */
  delete: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const program = await ctx.prisma.program.delete({
        where: { id: input.id },
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'DELETE',
        entityType: 'Program',
        entityId: input.id,
        detailsJson: { name: program.name, year: program.year },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return program
    }),

  /**
   * Get wizard config for a program (parsed from settingsJson)
   */
  getWizardConfig: protectedProcedure
    .input(z.object({ programId: z.string() }))
    .query(async ({ ctx, input }) => {
      const program = await ctx.prisma.program.findUniqueOrThrow({
        where: { id: input.programId },
        select: { settingsJson: true },
      })
      return parseWizardConfig(program.settingsJson)
    }),

  /**
   * Update wizard config for a program (admin only)
   */
  updateWizardConfig: adminProcedure
    .input(
      z.object({
        programId: z.string(),
        wizardConfig: wizardConfigSchema,
      })
    )
    .mutation(async ({ ctx, input }) => {
      const program = await ctx.prisma.program.findUniqueOrThrow({
        where: { id: input.programId },
        select: { settingsJson: true },
      })

      const currentSettings = (program.settingsJson || {}) as Record<string, unknown>

      const updatedSettings = {
        ...currentSettings,
        wizardConfig: input.wizardConfig,
      }

      await ctx.prisma.program.update({
        where: { id: input.programId },
        data: {
          settingsJson: updatedSettings as Prisma.InputJsonValue,
        },
      })

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'UPDATE',
        entityType: 'Program',
        entityId: input.programId,
        detailsJson: {
          field: 'wizardConfig',
          stepsEnabled: input.wizardConfig.steps.filter((s) => s.enabled).length,
          totalSteps: input.wizardConfig.steps.length,
          customFieldsCount: input.wizardConfig.customFields?.length ?? 0,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return { success: true }
    }),

  /**
   * Returns the merged edition-settings view for the admin Settings page:
   * Program fields (defaultAttendeeCap, visaStatusVisibleToMembers) plus the
   * LIVE_FINAL round's configJson values (attendeeEditCutoffHours and
   * confirmationWindowHours, with sensible defaults). Round-derived values
   * are null when the LIVE_FINAL round doesn't exist yet.
   */
  getEditionSettings: adminProcedure
    .input(z.object({ programId: z.string() }))
    .query(async ({ ctx, input }) => {
      const program = await ctx.prisma.program.findUniqueOrThrow({
        where: { id: input.programId },
        select: {
          id: true,
          defaultAttendeeCap: true,
          visaStatusVisibleToMembers: true,
        },
      })
      const round = await ctx.prisma.round.findFirst({
        where: {
          competition: { programId: input.programId },
          roundType: 'LIVE_FINAL',
        },
        orderBy: { sortOrder: 'desc' },
        select: { id: true, configJson: true },
      })
      const cfg = (round?.configJson ?? {}) as Record<string, unknown>
      return {
        programId: program.id,
        defaultAttendeeCap: program.defaultAttendeeCap,
        visaStatusVisibleToMembers: program.visaStatusVisibleToMembers,
        liveFinalRoundId: round?.id ?? null,
        attendeeEditCutoffHours: round
          ? ((cfg.attendeeEditCutoffHours as number | undefined) ?? 48)
          : null,
        confirmationWindowHours: round
          ? ((cfg.confirmationWindowHours as number | undefined) ?? 24)
          : null,
      }
    }),

  /**
   * Partial update for edition settings. Writes Program fields directly and
   * merges round-config keys (attendeeEditCutoffHours, confirmationWindowHours)
   * into the LIVE_FINAL round's configJson, preserving any unrelated keys
   * already in the JSON blob.
   */
  updateEditionSettings: adminProcedure
    .input(
      z.object({
        programId: z.string(),
        defaultAttendeeCap: z.number().int().min(1).max(20).optional(),
        visaStatusVisibleToMembers: z.boolean().optional(),
        attendeeEditCutoffHours: z.number().int().min(0).max(720).optional(),
        confirmationWindowHours: z.number().int().min(1).max(720).optional(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const programData: Record<string, unknown> = {}
      if (input.defaultAttendeeCap !== undefined) {
        programData.defaultAttendeeCap = input.defaultAttendeeCap
      }
      if (input.visaStatusVisibleToMembers !== undefined) {
        programData.visaStatusVisibleToMembers = input.visaStatusVisibleToMembers
      }

      if (Object.keys(programData).length > 0) {
        await ctx.prisma.program.update({
          where: { id: input.programId },
          data: programData,
        })
      }

      const roundConfigKeys = ['attendeeEditCutoffHours', 'confirmationWindowHours'] as const
      const roundUpdates: Record<string, number> = {}
      for (const k of roundConfigKeys) {
        const v = input[k]
        if (v !== undefined) roundUpdates[k] = v
      }
      if (Object.keys(roundUpdates).length > 0) {
        const round = await ctx.prisma.round.findFirst({
          where: {
            competition: { programId: input.programId },
            roundType: 'LIVE_FINAL',
          },
          orderBy: { sortOrder: 'desc' },
          select: { id: true, configJson: true },
        })
        if (round) {
          const existing = (round.configJson ?? {}) as Record<string, unknown>
          const merged = { ...existing, ...roundUpdates }
          await ctx.prisma.round.update({
            where: { id: round.id },
            data: { configJson: merged as Prisma.InputJsonValue },
          })
        }
      }

      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'PROGRAM_EDITION_SETTINGS_UPDATE',
        entityType: 'Program',
        entityId: input.programId,
        detailsJson: { ...input },
      })

      return { ok: true }
    }),

  /**
   * List CONFIRMED finalist projects for a program — used by the lunch
   * externals dialog to attach an external attendee to a team.
   */
  listFinalistProjects: adminProcedure
    .input(z.object({ programId: z.string() }))
    .query(({ ctx, input }) =>
      ctx.prisma.project.findMany({
        where: {
          programId: input.programId,
          finalistConfirmation: { status: 'CONFIRMED' },
        },
        select: { id: true, title: true },
        orderBy: { title: 'asc' },
      }),
    ),
})
