import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { Prisma } from '@prisma/client'
import { router, protectedProcedure, adminProcedure } from '../trpc'
import { getUserAvatarUrl } from '../utils/avatar-url'
import {
  notifyProjectTeam,
  NotificationTypes,
} from '../services/in-app-notification'

export const projectRouter = router({
  /**
   * List projects with filtering and pagination
   * Admin sees all, jury sees only assigned projects
   */
  list: protectedProcedure
    .input(
      z.object({
        programId: z.string().optional(),
        roundId: z.string().optional(),
        status: z
          .enum([
            'SUBMITTED',
            'ELIGIBLE',
            'ASSIGNED',
            'SEMIFINALIST',
            'FINALIST',
            'REJECTED',
          ])
          .optional(),
        statuses: z.array(
          z.enum([
            'SUBMITTED',
            'ELIGIBLE',
            'ASSIGNED',
            'SEMIFINALIST',
            'FINALIST',
            'REJECTED',
          ])
        ).optional(),
        notInRoundId: z.string().optional(), // Exclude projects already in this round
        unassignedOnly: z.boolean().optional(), // Projects not in any round
        search: z.string().optional(),
        tags: z.array(z.string()).optional(),
        competitionCategory: z.enum(['STARTUP', 'BUSINESS_CONCEPT']).optional(),
        oceanIssue: z.enum([
          'POLLUTION_REDUCTION', 'CLIMATE_MITIGATION', 'TECHNOLOGY_INNOVATION',
          'SUSTAINABLE_SHIPPING', 'BLUE_CARBON', 'HABITAT_RESTORATION',
          'COMMUNITY_CAPACITY', 'SUSTAINABLE_FISHING', 'CONSUMER_AWARENESS',
          'OCEAN_ACIDIFICATION', 'OTHER',
        ]).optional(),
        country: z.string().optional(),
        wantsMentorship: z.boolean().optional(),
        hasFiles: z.boolean().optional(),
        hasAssignments: z.boolean().optional(),
        page: z.number().int().min(1).default(1),
        perPage: z.number().int().min(1).max(5000).default(20),
      })
    )
    .query(async ({ ctx, input }) => {
      const {
        programId, roundId, notInRoundId, status, statuses, unassignedOnly, search, tags,
        competitionCategory, oceanIssue, country,
        wantsMentorship, hasFiles, hasAssignments,
        page, perPage,
      } = input
      const skip = (page - 1) * perPage

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

      // Filter by program via round
      if (programId) where.round = { programId }

      // Filter by round
      if (roundId) {
        where.roundId = roundId
      }

      // Exclude projects in a specific round
      if (notInRoundId) {
        where.roundId = { not: notInRoundId }
      }

      // Filter by unassigned (no round)
      if (unassignedOnly) {
        where.roundId = null
      }

      // Status filter
      if (statuses?.length || status) {
        const statusValues = statuses?.length ? statuses : status ? [status] : []
        if (statusValues.length > 0) {
          where.status = { in: statusValues }
        }
      }

      if (tags && tags.length > 0) {
        where.tags = { hasSome: tags }
      }
      if (competitionCategory) where.competitionCategory = competitionCategory
      if (oceanIssue) where.oceanIssue = oceanIssue
      if (country) where.country = country
      if (wantsMentorship !== undefined) where.wantsMentorship = wantsMentorship
      if (hasFiles === true) where.files = { some: {} }
      if (hasFiles === false) where.files = { none: {} }
      if (hasAssignments === true) where.assignments = { some: {} }
      if (hasAssignments === false) where.assignments = { none: {} }

      if (search) {
        where.OR = [
          { title: { contains: search, mode: 'insensitive' } },
          { teamName: { contains: search, mode: 'insensitive' } },
          { description: { contains: search, mode: 'insensitive' } },
        ]
      }

      // Jury members can only see assigned projects
      if (ctx.user.role === 'JURY_MEMBER') {
        where.assignments = {
          ...((where.assignments as Record<string, unknown>) || {}),
          some: { userId: ctx.user.id },
        }
      }

      const [projects, total] = await Promise.all([
        ctx.prisma.project.findMany({
          where,
          skip,
          take: perPage,
          orderBy: { createdAt: 'desc' },
          include: {
            files: true,
            round: {
              select: {
                id: true,
                name: true,
                program: { select: { id: true, name: true, year: true } },
              },
            },
            _count: { select: { assignments: true } },
          },
        }),
        ctx.prisma.project.count({ where }),
      ])

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

  /**
   * Get filter options for the project list (distinct values)
   */
  getFilterOptions: protectedProcedure
    .query(async ({ ctx }) => {
      const [rounds, countries, categories, issues] = await Promise.all([
        ctx.prisma.round.findMany({
          select: { id: true, name: true, program: { select: { name: true, year: true } } },
          orderBy: [{ program: { year: 'desc' } }, { createdAt: 'asc' }],
        }),
        ctx.prisma.project.findMany({
          where: { country: { not: null } },
          select: { country: true },
          distinct: ['country'],
          orderBy: { country: 'asc' },
        }),
        ctx.prisma.project.groupBy({
          by: ['competitionCategory'],
          where: { competitionCategory: { not: null } },
          _count: true,
        }),
        ctx.prisma.project.groupBy({
          by: ['oceanIssue'],
          where: { oceanIssue: { not: null } },
          _count: true,
        }),
      ])

      return {
        rounds,
        countries: countries.map((c) => c.country).filter(Boolean) as string[],
        categories: categories.map((c) => ({
          value: c.competitionCategory!,
          count: c._count,
        })),
        issues: issues.map((i) => ({
          value: i.oceanIssue!,
          count: i._count,
        })),
      }
    }),

  /**
   * Get a single project with details
   */
  get: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const project = await ctx.prisma.project.findUniqueOrThrow({
        where: { id: input.id },
        include: {
          files: true,
          round: true,
          teamMembers: {
            include: {
              user: {
                select: { id: true, name: true, email: true, profileImageKey: true, profileImageProvider: true },
              },
            },
            orderBy: { joinedAt: 'asc' },
          },
          mentorAssignment: {
            include: {
              mentor: {
                select: { id: true, name: true, email: true, expertiseTags: true, profileImageKey: true, profileImageProvider: true },
              },
            },
          },
        },
      })

      // Check access for jury members
      if (ctx.user.role === 'JURY_MEMBER') {
        const assignment = await ctx.prisma.assignment.findFirst({
          where: {
            projectId: input.id,
            userId: ctx.user.id,
          },
        })

        if (!assignment) {
          throw new TRPCError({
            code: 'FORBIDDEN',
            message: 'You are not assigned to this project',
          })
        }
      }

      // Attach avatar URLs to team members and mentor
      const teamMembersWithAvatars = await Promise.all(
        project.teamMembers.map(async (member) => ({
          ...member,
          user: {
            ...member.user,
            avatarUrl: await getUserAvatarUrl(member.user.profileImageKey, member.user.profileImageProvider),
          },
        }))
      )

      const mentorWithAvatar = project.mentorAssignment
        ? {
            ...project.mentorAssignment,
            mentor: {
              ...project.mentorAssignment.mentor,
              avatarUrl: await getUserAvatarUrl(
                project.mentorAssignment.mentor.profileImageKey,
                project.mentorAssignment.mentor.profileImageProvider
              ),
            },
          }
        : null

      return {
        ...project,
        teamMembers: teamMembersWithAvatars,
        mentorAssignment: mentorWithAvatar,
      }
    }),

  /**
   * Create a single project (admin only)
   * Projects belong to a round.
   */
  create: adminProcedure
    .input(
      z.object({
        roundId: z.string(),
        title: z.string().min(1).max(500),
        teamName: z.string().optional(),
        description: z.string().optional(),
        tags: z.array(z.string()).optional(),
        metadataJson: z.record(z.unknown()).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { metadataJson, ...rest } = input
      const project = await ctx.prisma.project.create({
        data: {
          ...rest,
          metadataJson: metadataJson as Prisma.InputJsonValue ?? undefined,
          status: 'SUBMITTED',
        },
      })

      // Audit log
      await ctx.prisma.auditLog.create({
        data: {
          userId: ctx.user.id,
          action: 'CREATE',
          entityType: 'Project',
          entityId: project.id,
          detailsJson: { title: input.title, roundId: input.roundId },
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        },
      })

      return project
    }),

  /**
   * Update a project (admin only)
   * Status updates require a roundId context since status is per-round.
   */
  update: adminProcedure
    .input(
      z.object({
        id: z.string(),
        title: z.string().min(1).max(500).optional(),
        teamName: z.string().optional().nullable(),
        description: z.string().optional().nullable(),
        // Status update requires roundId
        roundId: z.string().optional(),
        status: z
          .enum([
            'SUBMITTED',
            'ELIGIBLE',
            'ASSIGNED',
            'SEMIFINALIST',
            'FINALIST',
            'REJECTED',
          ])
          .optional(),
        tags: z.array(z.string()).optional(),
        metadataJson: z.record(z.unknown()).optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id, metadataJson, status, roundId, ...data } = input

      const project = await ctx.prisma.project.update({
        where: { id },
        data: {
          ...data,
          ...(status && { status }),
          metadataJson: metadataJson as Prisma.InputJsonValue ?? undefined,
        },
      })

      // Send notifications if status changed
      if (status) {
        // Get round details for notification
        const projectWithRound = await ctx.prisma.project.findUnique({
          where: { id },
          include: { round: { select: { name: true, entryNotificationType: true, program: { select: { name: true } } } } },
        })

        const round = projectWithRound?.round

        // Helper to get notification title based on type
        const getNotificationTitle = (type: string): string => {
          const titles: Record<string, string> = {
            ADVANCED_SEMIFINAL: "Congratulations! You're a Semi-Finalist",
            ADVANCED_FINAL: "Amazing News! You're a Finalist",
            NOT_SELECTED: 'Application Status Update',
            WINNER_ANNOUNCEMENT: 'Congratulations! You Won!',
          }
          return titles[type] || 'Project Update'
        }

        // Helper to get notification message based on type
        const getNotificationMessage = (type: string, projectName: string): string => {
          const messages: Record<string, (name: string) => string> = {
            ADVANCED_SEMIFINAL: (name) => `Your project "${name}" has advanced to the semi-finals!`,
            ADVANCED_FINAL: (name) => `Your project "${name}" has been selected as a finalist!`,
            NOT_SELECTED: (name) => `We regret to inform you that "${name}" was not selected for the next round.`,
            WINNER_ANNOUNCEMENT: (name) => `Your project "${name}" has been selected as a winner!`,
          }
          return messages[type]?.(projectName) || `Update regarding your project "${projectName}".`
        }

        // Use round's configured notification type, or fall back to status-based defaults
        if (round?.entryNotificationType) {
          await notifyProjectTeam(id, {
            type: round.entryNotificationType,
            title: getNotificationTitle(round.entryNotificationType),
            message: getNotificationMessage(round.entryNotificationType, project.title),
            linkUrl: `/team/projects/${id}`,
            linkLabel: 'View Project',
            priority: round.entryNotificationType === 'NOT_SELECTED' ? 'normal' : 'high',
            metadata: {
              projectName: project.title,
              roundName: round.name,
              programName: round.program?.name,
            },
          })
        } else if (round) {
          // Fall back to hardcoded status-based notifications
          const notificationConfig: Record<
            string,
            { type: string; title: string; message: string }
          > = {
            SEMIFINALIST: {
              type: NotificationTypes.ADVANCED_SEMIFINAL,
              title: "Congratulations! You're a Semi-Finalist",
              message: `Your project "${project.title}" has advanced to the semi-finals!`,
            },
            FINALIST: {
              type: NotificationTypes.ADVANCED_FINAL,
              title: "Amazing News! You're a Finalist",
              message: `Your project "${project.title}" has been selected as a finalist!`,
            },
            REJECTED: {
              type: NotificationTypes.NOT_SELECTED,
              title: 'Application Status Update',
              message: `We regret to inform you that "${project.title}" was not selected for the next round.`,
            },
          }

          const config = notificationConfig[status]
          if (config) {
            await notifyProjectTeam(id, {
              type: config.type,
              title: config.title,
              message: config.message,
              linkUrl: `/team/projects/${id}`,
              linkLabel: 'View Project',
              priority: status === 'REJECTED' ? 'normal' : 'high',
              metadata: {
                projectName: project.title,
                roundName: round?.name,
                programName: round?.program?.name,
              },
            })
          }
        }
      }

      // Audit log
      await ctx.prisma.auditLog.create({
        data: {
          userId: ctx.user.id,
          action: 'UPDATE',
          entityType: 'Project',
          entityId: id,
          detailsJson: { ...data, status, metadataJson } as Prisma.InputJsonValue,
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        },
      })

      return project
    }),

  /**
   * Delete a project (admin only)
   */
  delete: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const project = await ctx.prisma.project.delete({
        where: { id: input.id },
      })

      // Audit log
      await ctx.prisma.auditLog.create({
        data: {
          userId: ctx.user.id,
          action: 'DELETE',
          entityType: 'Project',
          entityId: input.id,
          detailsJson: { title: project.title },
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        },
      })

      return project
    }),

  /**
   * Import projects from CSV data (admin only)
   * Projects belong to a program. Optionally assign to a round.
   */
  importCSV: adminProcedure
    .input(
      z.object({
        programId: z.string(),
        roundId: z.string().optional(),
        projects: z.array(
          z.object({
            title: z.string().min(1),
            teamName: z.string().optional(),
            description: z.string().optional(),
            tags: z.array(z.string()).optional(),
            metadataJson: z.record(z.unknown()).optional(),
          })
        ),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify program exists
      await ctx.prisma.program.findUniqueOrThrow({
        where: { id: input.programId },
      })

      // Verify round exists and belongs to program if provided
      if (input.roundId) {
        const round = await ctx.prisma.round.findUniqueOrThrow({
          where: { id: input.roundId },
        })
        if (round.programId !== input.programId) {
          throw new TRPCError({
            code: 'BAD_REQUEST',
            message: 'Round does not belong to the selected program',
          })
        }
      }

      // Create projects in a transaction
      const result = await ctx.prisma.$transaction(async (tx) => {
        // Create all projects with roundId
        const projectData = input.projects.map((p) => {
          const { metadataJson, ...rest } = p
          return {
            ...rest,
            roundId: input.roundId!,
            status: 'SUBMITTED' as const,
            metadataJson: metadataJson as Prisma.InputJsonValue ?? undefined,
          }
        })

        const created = await tx.project.createManyAndReturn({
          data: projectData,
          select: { id: true },
        })

        return { imported: created.length }
      })

      // Audit log
      await ctx.prisma.auditLog.create({
        data: {
          userId: ctx.user.id,
          action: 'IMPORT',
          entityType: 'Project',
          detailsJson: { programId: input.programId, roundId: input.roundId, count: result.imported },
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        },
      })

      return result
    }),

  /**
   * Get all unique tags used in projects
   */
  getTags: protectedProcedure
    .input(z.object({
      roundId: z.string().optional(),
      programId: z.string().optional(),
    }))
    .query(async ({ ctx, input }) => {
      const where: Record<string, unknown> = {}
      if (input.programId) where.round = { programId: input.programId }
      if (input.roundId) where.roundId = input.roundId

      const projects = await ctx.prisma.project.findMany({
        where: Object.keys(where).length > 0 ? where : undefined,
        select: { tags: true },
      })

      const allTags = projects.flatMap((p) => p.tags)
      const uniqueTags = [...new Set(allTags)].sort()

      return uniqueTags
    }),

  /**
   * Update project status in bulk (admin only)
   * Status is per-round, so roundId is required.
   */
  bulkUpdateStatus: adminProcedure
    .input(
      z.object({
        ids: z.array(z.string()),
        roundId: z.string(),
        status: z.enum([
          'SUBMITTED',
          'ELIGIBLE',
          'ASSIGNED',
          'SEMIFINALIST',
          'FINALIST',
          'REJECTED',
        ]),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const updated = await ctx.prisma.project.updateMany({
        where: {
          id: { in: input.ids },
          roundId: input.roundId,
        },
        data: { status: input.status },
      })

      // Audit log
      await ctx.prisma.auditLog.create({
        data: {
          userId: ctx.user.id,
          action: 'BULK_UPDATE_STATUS',
          entityType: 'Project',
          detailsJson: { ids: input.ids, roundId: input.roundId, status: input.status },
          ipAddress: ctx.ip,
          userAgent: ctx.userAgent,
        },
      })

      // Get round details including configured notification type
      const [projects, round] = await Promise.all([
        input.ids.length > 0
          ? ctx.prisma.project.findMany({
              where: { id: { in: input.ids } },
              select: { id: true, title: true },
            })
          : Promise.resolve([]),
        ctx.prisma.round.findUnique({
          where: { id: input.roundId },
          select: { name: true, entryNotificationType: true, program: { select: { name: true } } },
        }),
      ])

      // Helper to get notification title based on type
      const getNotificationTitle = (type: string): string => {
        const titles: Record<string, string> = {
          ADVANCED_SEMIFINAL: "Congratulations! You're a Semi-Finalist",
          ADVANCED_FINAL: "Amazing News! You're a Finalist",
          NOT_SELECTED: 'Application Status Update',
          WINNER_ANNOUNCEMENT: 'Congratulations! You Won!',
        }
        return titles[type] || 'Project Update'
      }

      // Helper to get notification message based on type
      const getNotificationMessage = (type: string, projectName: string): string => {
        const messages: Record<string, (name: string) => string> = {
          ADVANCED_SEMIFINAL: (name) => `Your project "${name}" has advanced to the semi-finals!`,
          ADVANCED_FINAL: (name) => `Your project "${name}" has been selected as a finalist!`,
          NOT_SELECTED: (name) => `We regret to inform you that "${name}" was not selected for the next round.`,
          WINNER_ANNOUNCEMENT: (name) => `Your project "${name}" has been selected as a winner!`,
        }
        return messages[type]?.(projectName) || `Update regarding your project "${projectName}".`
      }

      // Notify project teams based on round's configured notification or status-based fallback
      if (projects.length > 0) {
        if (round?.entryNotificationType) {
          // Use round's configured notification type
          for (const project of projects) {
            await notifyProjectTeam(project.id, {
              type: round.entryNotificationType,
              title: getNotificationTitle(round.entryNotificationType),
              message: getNotificationMessage(round.entryNotificationType, project.title),
              linkUrl: `/team/projects/${project.id}`,
              linkLabel: 'View Project',
              priority: round.entryNotificationType === 'NOT_SELECTED' ? 'normal' : 'high',
              metadata: {
                projectName: project.title,
                roundName: round.name,
                programName: round.program?.name,
              },
            })
          }
        } else {
          // Fall back to hardcoded status-based notifications
          const notificationConfig: Record<
            string,
            { type: string; titleFn: (name: string) => string; messageFn: (name: string) => string }
          > = {
            SEMIFINALIST: {
              type: NotificationTypes.ADVANCED_SEMIFINAL,
              titleFn: () => "Congratulations! You're a Semi-Finalist",
              messageFn: (name) => `Your project "${name}" has advanced to the semi-finals!`,
            },
            FINALIST: {
              type: NotificationTypes.ADVANCED_FINAL,
              titleFn: () => "Amazing News! You're a Finalist",
              messageFn: (name) => `Your project "${name}" has been selected as a finalist!`,
            },
            REJECTED: {
              type: NotificationTypes.NOT_SELECTED,
              titleFn: () => 'Application Status Update',
              messageFn: (name) =>
                `We regret to inform you that "${name}" was not selected for the next round.`,
            },
          }

          const config = notificationConfig[input.status]
          if (config) {
            for (const project of projects) {
              await notifyProjectTeam(project.id, {
                type: config.type,
                title: config.titleFn(project.title),
                message: config.messageFn(project.title),
                linkUrl: `/team/projects/${project.id}`,
                linkLabel: 'View Project',
                priority: input.status === 'REJECTED' ? 'normal' : 'high',
                metadata: {
                  projectName: project.title,
                  roundName: round?.name,
                  programName: round?.program?.name,
                },
              })
            }
          }
        }
      }

      return { updated: updated.count }
    }),

  /**
   * List projects in a program's pool (not assigned to any round)
   */
  listPool: adminProcedure
    .input(
      z.object({
        programId: z.string(),
        search: z.string().optional(),
        page: z.number().int().min(1).default(1),
        perPage: z.number().int().min(1).max(100).default(50),
      })
    )
    .query(async ({ ctx, input }) => {
      const { programId, search, page, perPage } = input
      const skip = (page - 1) * perPage

      const where: Record<string, unknown> = {
        round: { programId },
        roundId: null,
      }

      if (search) {
        where.OR = [
          { title: { contains: search, mode: 'insensitive' } },
          { teamName: { contains: search, mode: 'insensitive' } },
        ]
      }

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

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