import crypto from 'crypto'
import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, publicProcedure, protectedProcedure } from '../trpc'
import { getPresignedUrl, generateObjectKey } from '@/lib/minio'
import { sendStyledNotificationEmail, sendTeamMemberInviteEmail } from '@/lib/email'
import { logAudit } from '@/server/utils/audit'
import { createNotification } from '../services/in-app-notification'

// Bucket for applicant submissions
export const SUBMISSIONS_BUCKET = 'mopc-submissions'
const TEAM_INVITE_TOKEN_EXPIRY_MS = 30 * 24 * 60 * 60 * 1000 // 30 days

function generateInviteToken(): string {
  return crypto.randomBytes(32).toString('hex')
}

export const applicantRouter = router({
  /**
   * Get submission info for an applicant (by round slug)
   */
  getSubmissionBySlug: publicProcedure
    .input(z.object({ slug: z.string() }))
    .query(async ({ ctx, input }) => {
      const round = await ctx.prisma.round.findFirst({
        where: { slug: input.slug },
        include: {
          competition: {
            include: {
              program: { select: { id: true, name: true, year: true, description: true } },
            },
          },
        },
      })

      if (!round) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Round not found',
        })
      }

      const now = new Date()
      const isOpen = round.status === 'ROUND_ACTIVE'

      return {
        stage: {
          id: round.id,
          name: round.name,
          slug: round.slug,
          windowCloseAt: null,
          isOpen,
        },
        program: round.competition.program,
      }
    }),

  /**
   * Get the current user's submission for a round (as submitter or team member)
   */
  getMySubmission: protectedProcedure
    .input(z.object({ roundId: z.string().optional(), programId: z.string().optional() }))
    .query(async ({ ctx, input }) => {
      // Only applicants can use this
      if (ctx.user.role !== 'APPLICANT') {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'Only applicants can access submissions',
        })
      }

      const where: Record<string, unknown> = {
        OR: [
          { submittedByUserId: ctx.user.id },
          {
            teamMembers: {
              some: { userId: ctx.user.id },
            },
          },
        ],
      }

      if (input.roundId) {
        where.roundAssignments = { some: { roundId: input.roundId } }
      }
      if (input.programId) {
        where.programId = input.programId
      }

      const project = await ctx.prisma.project.findFirst({
        where,
        include: {
          files: true,
          program: { select: { id: true, name: true, year: true } },
          teamMembers: {
            include: {
              user: {
                select: { id: true, name: true, email: true },
              },
            },
          },
        },
      })

      if (project) {
        const userMembership = project.teamMembers.find((tm) => tm.userId === ctx.user.id)
        return {
          ...project,
          userRole: userMembership?.role || (project.submittedByUserId === ctx.user.id ? 'LEAD' : null),
          isTeamLead: project.submittedByUserId === ctx.user.id || userMembership?.role === 'LEAD',
        }
      }

      return null
    }),

  /**
   * Create or update a submission (draft or submitted)
   */
  saveSubmission: protectedProcedure
    .input(
      z.object({
        programId: z.string().optional(),
        projectId: z.string().optional(),
        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(),
        submit: z.boolean().default(false),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Only applicants can use this
      if (ctx.user.role !== 'APPLICANT') {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'Only applicants can submit projects',
        })
      }

      const now = new Date()

      const { projectId, submit, programId, metadataJson, ...data } = input

      if (projectId) {
        // Update existing
        const existing = await ctx.prisma.project.findFirst({
          where: {
            id: projectId,
            submittedByUserId: ctx.user.id,
          },
        })

        if (!existing) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: 'Project not found or you do not have access',
          })
        }

        // Can't update if already submitted
        if (existing.submittedAt && !submit) {
          throw new TRPCError({
            code: 'BAD_REQUEST',
            message: 'Cannot modify a submitted project',
          })
        }

        const project = await ctx.prisma.project.update({
          where: { id: projectId },
          data: {
            ...data,
            metadataJson: metadataJson as unknown ?? undefined,
            submittedAt: submit && !existing.submittedAt ? now : existing.submittedAt,
          },
        })

        // Update Project status if submitting
        if (submit) {
          await ctx.prisma.project.update({
            where: { id: projectId },
            data: { status: 'SUBMITTED' },
          })
        }

        return project
      } else {
        if (!programId) {
          throw new TRPCError({
            code: 'BAD_REQUEST',
            message: 'programId is required when creating a new submission',
          })
        }

        // Create new project
        const project = await ctx.prisma.project.create({
          data: {
            programId,
            ...data,
            metadataJson: metadataJson as unknown ?? undefined,
            submittedByUserId: ctx.user.id,
            submittedByEmail: ctx.user.email,
            submissionSource: 'MANUAL',
            submittedAt: submit ? now : null,
            status: 'SUBMITTED',
          },
        })

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

        return project
      }
    }),

  /**
   * Get upload URL for a submission file
   */
  getUploadUrl: protectedProcedure
    .input(
      z.object({
        projectId: z.string(),
        fileName: z.string(),
        mimeType: z.string(),
        fileType: z.enum(['EXEC_SUMMARY', 'BUSINESS_PLAN', 'VIDEO_PITCH', 'PRESENTATION', 'SUPPORTING_DOC', 'OTHER']),
        roundId: z.string().optional(),
        requirementId: z.string().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Applicants or team members can upload
      if (ctx.user.role !== 'APPLICANT') {
        // Check if user is a team member of the project
        const teamMembership = await ctx.prisma.teamMember.findFirst({
          where: { projectId: input.projectId, userId: ctx.user.id },
          select: { id: true },
        })
        if (!teamMembership) {
          throw new TRPCError({
            code: 'FORBIDDEN',
            message: 'Only applicants or team members can upload files',
          })
        }
      }

      // Verify project access (owner or team member)
      const project = await ctx.prisma.project.findFirst({
        where: {
          id: input.projectId,
          OR: [
            { submittedByUserId: ctx.user.id },
            { teamMembers: { some: { userId: ctx.user.id } } },
          ],
        },
      })

      if (!project) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Project not found or you do not have access',
        })
      }

      // If uploading against a requirement, validate mime type and size
      if (input.requirementId) {
        const requirement = await ctx.prisma.fileRequirement.findUnique({
          where: { id: input.requirementId },
        })
        if (!requirement) {
          throw new TRPCError({ code: 'NOT_FOUND', message: 'File requirement not found' })
        }
        // Validate mime type
        if (requirement.acceptedMimeTypes.length > 0) {
          const accepted = requirement.acceptedMimeTypes.some((pattern) => {
            if (pattern.endsWith('/*')) {
              return input.mimeType.startsWith(pattern.replace('/*', '/'))
            }
            return input.mimeType === pattern
          })
          if (!accepted) {
            throw new TRPCError({
              code: 'BAD_REQUEST',
              message: `File type ${input.mimeType} is not accepted. Accepted types: ${requirement.acceptedMimeTypes.join(', ')}`,
            })
          }
        }
      }

      let isLate = false

      // Can't upload if already submitted
      if (project.submittedAt && !isLate) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Cannot modify a submitted project',
        })
      }

      // Fetch round name for storage path (if uploading against a round)
      let roundName: string | undefined
      if (input.roundId) {
        const round = await ctx.prisma.round.findUnique({
          where: { id: input.roundId },
          select: { name: true },
        })
        roundName = round?.name
      }

      const objectKey = generateObjectKey(project.title, input.fileName, roundName)

      const url = await getPresignedUrl(SUBMISSIONS_BUCKET, objectKey, 'PUT', 3600)

      return {
        url,
        bucket: SUBMISSIONS_BUCKET,
        objectKey,
        isLate,
        roundId: input.roundId || null,
      }
    }),

  /**
   * Save file metadata after upload
   */
  saveFileMetadata: protectedProcedure
    .input(
      z.object({
        projectId: z.string(),
        fileName: z.string(),
        mimeType: z.string(),
        size: z.number().int(),
        fileType: z.enum(['EXEC_SUMMARY', 'BUSINESS_PLAN', 'VIDEO_PITCH', 'PRESENTATION', 'SUPPORTING_DOC', 'OTHER']),
        bucket: z.string(),
        objectKey: z.string(),
        roundId: z.string().optional(),
        isLate: z.boolean().optional(),
        requirementId: z.string().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Applicants or team members can save files
      if (ctx.user.role !== 'APPLICANT') {
        const teamMembership = await ctx.prisma.teamMember.findFirst({
          where: { projectId: input.projectId, userId: ctx.user.id },
          select: { id: true },
        })
        if (!teamMembership) {
          throw new TRPCError({
            code: 'FORBIDDEN',
            message: 'Only applicants or team members can save files',
          })
        }
      }

      // Verify project access
      const project = await ctx.prisma.project.findFirst({
        where: {
          id: input.projectId,
          OR: [
            { submittedByUserId: ctx.user.id },
            { teamMembers: { some: { userId: ctx.user.id } } },
          ],
        },
      })

      if (!project) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Project not found or you do not have access',
        })
      }

      const { projectId, roundId, isLate, requirementId, ...fileData } = input

      // Delete existing file: by requirementId if provided, otherwise by fileType
      if (requirementId) {
        await ctx.prisma.projectFile.deleteMany({
          where: {
            projectId,
            requirementId,
          },
        })
      } else {
        await ctx.prisma.projectFile.deleteMany({
          where: {
            projectId,
            fileType: input.fileType,
          },
        })
      }

      // Create new file record
      const file = await ctx.prisma.projectFile.create({
        data: {
          projectId,
          ...fileData,
          roundId: roundId || null,
          isLate: isLate || false,
          requirementId: requirementId || null,
        },
      })

      return file
    }),

  /**
   * Delete a file from submission
   */
  deleteFile: protectedProcedure
    .input(z.object({ fileId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const file = await ctx.prisma.projectFile.findUniqueOrThrow({
        where: { id: input.fileId },
        include: { project: { include: { teamMembers: { select: { userId: true } } } } },
      })

      // Verify ownership or team membership
      const isOwner = file.project.submittedByUserId === ctx.user.id
      const isTeamMember = file.project.teamMembers.some((tm) => tm.userId === ctx.user.id)

      if (!isOwner && !isTeamMember) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'You do not have access to this file',
        })
      }

      // Can't delete if project is submitted
      if (file.project.submittedAt) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Cannot modify a submitted project',
        })
      }

      await ctx.prisma.projectFile.delete({
        where: { id: input.fileId },
      })

      return { success: true }
    }),

  /**
   * Get status timeline from ProjectStatusHistory
   */
  getStatusTimeline: protectedProcedure
    .input(z.object({ projectId: z.string() }))
    .query(async ({ ctx, input }) => {
      // Verify user has access to this project
      const project = await ctx.prisma.project.findFirst({
        where: {
          id: input.projectId,
          OR: [
            { submittedByUserId: ctx.user.id },
            {
              teamMembers: {
                some: { userId: ctx.user.id },
              },
            },
          ],
        },
        select: { id: true },
      })

      if (!project) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Project not found',
        })
      }

      const history = await ctx.prisma.projectStatusHistory.findMany({
        where: { projectId: input.projectId },
        orderBy: { changedAt: 'asc' },
        select: {
          status: true,
          changedAt: true,
          changedBy: true,
        },
      })

      return history
    }),

  /**
   * Get submission status timeline
   */
  getSubmissionStatus: protectedProcedure
    .input(z.object({ projectId: z.string() }))
    .query(async ({ ctx, input }) => {
      const project = await ctx.prisma.project.findFirst({
        where: {
          id: input.projectId,
          OR: [
            { submittedByUserId: ctx.user.id },
            {
              teamMembers: {
                some: { userId: ctx.user.id },
              },
            },
          ],
        },
        include: {
          program: { select: { id: true, name: true, year: true } },
          files: true,
          teamMembers: {
            include: {
              user: {
                select: { id: true, name: true, email: true },
              },
            },
          },
          wonAwards: {
            select: { id: true, name: true },
          },
        },
      })

      if (!project) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Project not found',
        })
      }

      // Get the project status
      const currentStatus = project.status ?? 'SUBMITTED'

      // Fetch actual status history
      const statusHistory = await ctx.prisma.projectStatusHistory.findMany({
        where: { projectId: input.projectId },
        orderBy: { changedAt: 'asc' },
        select: { status: true, changedAt: true },
      })

      // Build a map of status -> earliest changedAt
      const statusDateMap = new Map<string, Date>()
      for (const entry of statusHistory) {
        if (!statusDateMap.has(entry.status)) {
          statusDateMap.set(entry.status, entry.changedAt)
        }
      }

      const isRejected = currentStatus === 'REJECTED'
      const hasWonAward = project.wonAwards.length > 0

      // Build timeline - handle REJECTED as terminal state
      const timeline = [
        {
          status: 'CREATED',
          label: 'Application Started',
          date: project.createdAt,
          completed: true,
          isTerminal: false,
        },
        {
          status: 'SUBMITTED',
          label: 'Application Submitted',
          date: project.submittedAt || statusDateMap.get('SUBMITTED') || null,
          completed: !!project.submittedAt || statusDateMap.has('SUBMITTED'),
          isTerminal: false,
        },
        {
          status: 'UNDER_REVIEW',
          label: 'Under Review',
          date: statusDateMap.get('ELIGIBLE') || statusDateMap.get('ASSIGNED') ||
            (currentStatus !== 'SUBMITTED' && project.submittedAt ? project.submittedAt : null),
          completed: ['ELIGIBLE', 'ASSIGNED', 'SEMIFINALIST', 'FINALIST', 'REJECTED'].includes(currentStatus),
          isTerminal: false,
        },
      ]

      if (isRejected) {
        // For rejected projects, show REJECTED as the terminal red step
        timeline.push({
          status: 'REJECTED',
          label: 'Not Selected',
          date: statusDateMap.get('REJECTED') || null,
          completed: true,
          isTerminal: true,
        })
      } else {
        // Normal progression
        timeline.push(
          {
            status: 'SEMIFINALIST',
            label: 'Semi-finalist',
            date: statusDateMap.get('SEMIFINALIST') || null,
            completed: ['SEMIFINALIST', 'FINALIST'].includes(currentStatus) || hasWonAward,
            isTerminal: false,
          },
          {
            status: 'FINALIST',
            label: 'Finalist',
            date: statusDateMap.get('FINALIST') || null,
            completed: currentStatus === 'FINALIST' || hasWonAward,
            isTerminal: false,
          },
        )

        if (hasWonAward) {
          timeline.push({
            status: 'WINNER',
            label: `Winner${project.wonAwards.length > 0 ? ` - ${project.wonAwards[0].name}` : ''}`,
            date: null,
            completed: true,
            isTerminal: false,
          })
        }
      }

      return {
        project,
        timeline,
        currentStatus,
      }
    }),

  /**
   * List all submissions for current user (including as team member)
   */
  listMySubmissions: protectedProcedure.query(async ({ ctx }) => {
    if (ctx.user.role !== 'APPLICANT') {
      throw new TRPCError({
        code: 'FORBIDDEN',
        message: 'Only applicants can access submissions',
      })
    }

    // Find projects where user is either the submitter OR a team member
    const projects = await ctx.prisma.project.findMany({
      where: {
        OR: [
          { submittedByUserId: ctx.user.id },
          {
            teamMembers: {
              some: { userId: ctx.user.id },
            },
          },
        ],
      },
      include: {
        program: { select: { id: true, name: true, year: true } },
        files: true,
        teamMembers: {
          include: {
            user: {
              select: { id: true, name: true, email: true },
            },
          },
        },
      },
      orderBy: { createdAt: 'desc' },
    })

    // Add user's role in each project
    return projects.map((project) => {
      const userMembership = project.teamMembers.find((tm) => tm.userId === ctx.user.id)
      return {
        ...project,
        userRole: userMembership?.role || (project.submittedByUserId === ctx.user.id ? 'LEAD' : null),
        isTeamLead: project.submittedByUserId === ctx.user.id || userMembership?.role === 'LEAD',
      }
    })
  }),

  /**
   * Get team members for a project
   */
  getTeamMembers: protectedProcedure
    .input(z.object({ projectId: z.string() }))
    .query(async ({ ctx, input }) => {
      // Verify user has access to this project
      const project = await ctx.prisma.project.findFirst({
        where: {
          id: input.projectId,
          OR: [
            { submittedByUserId: ctx.user.id },
            {
              teamMembers: {
                some: { userId: ctx.user.id },
              },
            },
          ],
        },
        include: {
          teamMembers: {
            include: {
              user: {
                select: {
                  id: true,
                  name: true,
                  email: true,
                  status: true,
                  lastLoginAt: true,
                },
              },
            },
            orderBy: { joinedAt: 'asc' },
          },
          submittedBy: {
            select: { id: true, name: true, email: true },
          },
        },
      })

      if (!project) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Project not found or you do not have access',
        })
      }

      return {
        teamMembers: project.teamMembers,
        submittedBy: project.submittedBy,
      }
    }),

  /**
   * Invite a new team member
   */
  inviteTeamMember: protectedProcedure
    .input(
      z.object({
        projectId: z.string(),
        email: z.string().email(),
        name: z.string().min(1),
        role: z.enum(['MEMBER', 'ADVISOR']),
        title: z.string().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const normalizedEmail = input.email.trim().toLowerCase()

      // Verify user is team lead
      const project = await ctx.prisma.project.findFirst({
        where: {
          id: input.projectId,
          OR: [
            { submittedByUserId: ctx.user.id },
            {
              teamMembers: {
                some: {
                  userId: ctx.user.id,
                  role: 'LEAD',
                },
              },
            },
          ],
        },
      })

      if (!project) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'Only team leads can invite new members',
        })
      }

      // Check if already a team member
      const existingMember = await ctx.prisma.teamMember.findFirst({
        where: {
          projectId: input.projectId,
          user: { email: normalizedEmail },
        },
      })

      if (existingMember) {
        throw new TRPCError({
          code: 'CONFLICT',
          message: 'This person is already a team member',
        })
      }

      // Find or create user
      let user = await ctx.prisma.user.findUnique({
        where: { email: normalizedEmail },
      })

      if (!user) {
        user = await ctx.prisma.user.create({
          data: {
            email: normalizedEmail,
            name: input.name,
            role: 'APPLICANT',
            status: 'NONE',
          },
        })
      }

      if (user.status === 'SUSPENDED') {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'This user account is suspended and cannot be invited',
        })
      }

      const teamLeadName = ctx.user.name?.trim() || 'A team lead'
      const baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000'
      const requiresAccountSetup = user.status !== 'ACTIVE'

      try {
        if (requiresAccountSetup) {
          const token = generateInviteToken()
          await ctx.prisma.user.update({
            where: { id: user.id },
            data: {
              status: 'INVITED',
              inviteToken: token,
              inviteTokenExpiresAt: new Date(Date.now() + TEAM_INVITE_TOKEN_EXPIRY_MS),
            },
          })

          const inviteUrl = `${baseUrl}/accept-invite?token=${token}`
          await sendTeamMemberInviteEmail(
            user.email,
            user.name || input.name,
            project.title,
            teamLeadName,
            inviteUrl
          )
        } else {
          await sendStyledNotificationEmail(
            user.email,
            user.name || input.name,
            'TEAM_INVITATION',
            {
              title: 'You were added to a project team',
              message: `${teamLeadName} added you to the project "${project.title}".`,
              linkUrl: `${baseUrl}/applicant/team`,
              linkLabel: 'Open Team',
              metadata: {
                projectId: project.id,
                projectName: project.title,
              },
            },
            `You've been added to "${project.title}"`
          )
        }
      } catch (error) {
        try {
          await ctx.prisma.notificationLog.create({
            data: {
              userId: user.id,
              channel: 'EMAIL',
              provider: 'SMTP',
              type: 'TEAM_INVITATION',
              status: 'FAILED',
              errorMsg: error instanceof Error ? error.message : 'Unknown error',
            },
          })
        } catch {
          // Never fail on notification logging
        }

        throw new TRPCError({
          code: 'INTERNAL_SERVER_ERROR',
          message: 'Failed to send invitation email. Please try again.',
        })
      }

      // Create team membership
      const teamMember = await ctx.prisma.teamMember.create({
        data: {
          projectId: input.projectId,
          userId: user.id,
          role: input.role,
          title: input.title,
        },
        include: {
          user: {
            select: { id: true, name: true, email: true, status: true },
          },
        },
      })

      try {
        await ctx.prisma.notificationLog.create({
          data: {
            userId: user.id,
            channel: 'EMAIL',
            provider: 'SMTP',
            type: 'TEAM_INVITATION',
            status: 'SENT',
          },
        })
      } catch {
        // Never fail on notification logging
      }

      try {
        await createNotification({
          userId: user.id,
          type: 'TEAM_INVITATION',
          title: 'Team Invitation',
          message: `${teamLeadName} added you to "${project.title}"`,
          linkUrl: '/applicant/team',
          linkLabel: 'View Team',
          priority: 'normal',
          metadata: {
            projectId: project.id,
            projectName: project.title,
          },
        })
      } catch {
        // Never fail invitation flow on in-app notification issues
      }

      return {
        teamMember,
        inviteEmailSent: true,
        requiresAccountSetup,
      }
    }),

  /**
   * Remove a team member
   */
  removeTeamMember: protectedProcedure
    .input(
      z.object({
        projectId: z.string(),
        userId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify user is team lead
      const project = await ctx.prisma.project.findFirst({
        where: {
          id: input.projectId,
          OR: [
            { submittedByUserId: ctx.user.id },
            {
              teamMembers: {
                some: {
                  userId: ctx.user.id,
                  role: 'LEAD',
                },
              },
            },
          ],
        },
      })

      if (!project) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'Only team leads can remove members',
        })
      }

      // Can't remove the original submitter
      if (project.submittedByUserId === input.userId) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'Cannot remove the original applicant from the team',
        })
      }

      await ctx.prisma.teamMember.deleteMany({
        where: {
          projectId: input.projectId,
          userId: input.userId,
        },
      })

      return { success: true }
    }),

  /**
   * Send a message to the assigned mentor
   */
  sendMentorMessage: protectedProcedure
    .input(
      z.object({
        projectId: z.string(),
        message: z.string().min(1).max(5000),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Verify user is part of this project team
      const project = await ctx.prisma.project.findFirst({
        where: {
          id: input.projectId,
          OR: [
            { submittedByUserId: ctx.user.id },
            {
              teamMembers: {
                some: { userId: ctx.user.id },
              },
            },
          ],
        },
        include: {
          mentorAssignment: { select: { mentorId: true } },
        },
      })

      if (!project) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Project not found or you do not have access',
        })
      }

      if (!project.mentorAssignment) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'No mentor assigned to this project',
        })
      }

      const mentorMessage = await ctx.prisma.mentorMessage.create({
        data: {
          projectId: input.projectId,
          senderId: ctx.user.id,
          message: input.message,
        },
        include: {
          sender: {
            select: { id: true, name: true, email: true },
          },
        },
      })

      // Notify the mentor
      await createNotification({
        userId: project.mentorAssignment.mentorId,
        type: 'MENTOR_MESSAGE',
        title: 'New Message',
        message: `${ctx.user.name || 'Team member'} sent a message about "${project.title}"`,
        linkUrl: `/mentor/projects/${input.projectId}`,
        linkLabel: 'View Message',
        priority: 'normal',
        metadata: {
          projectId: input.projectId,
          projectName: project.title,
        },
      })

      return mentorMessage
    }),

  /**
   * Get mentor messages for a project (applicant side)
   */
  getMentorMessages: protectedProcedure
    .input(z.object({ projectId: z.string() }))
    .query(async ({ ctx, input }) => {
      // Verify user is part of this project team
      const project = await ctx.prisma.project.findFirst({
        where: {
          id: input.projectId,
          OR: [
            { submittedByUserId: ctx.user.id },
            {
              teamMembers: {
                some: { userId: ctx.user.id },
              },
            },
          ],
        },
        select: { id: true },
      })

      if (!project) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Project not found or you do not have access',
        })
      }

      const messages = await ctx.prisma.mentorMessage.findMany({
        where: { projectId: input.projectId },
        include: {
          sender: {
            select: { id: true, name: true, email: true, role: true },
          },
        },
        orderBy: { createdAt: 'asc' },
      })

      // Mark unread messages from mentor as read
      await ctx.prisma.mentorMessage.updateMany({
        where: {
          projectId: input.projectId,
          senderId: { not: ctx.user.id },
          isRead: false,
        },
        data: { isRead: true },
      })

      return messages
    }),

  /**
   * Get the applicant's dashboard data: their project (latest edition),
   * team members, open rounds for document submission, and status timeline.
   */
  getMyDashboard: protectedProcedure.query(async ({ ctx }) => {
    if (ctx.user.role !== 'APPLICANT') {
      throw new TRPCError({
        code: 'FORBIDDEN',
        message: 'Only applicants can access this',
      })
    }

    // Find the applicant's project (most recent, from active edition if possible)
    const project = await ctx.prisma.project.findFirst({
      where: {
        OR: [
          { submittedByUserId: ctx.user.id },
          { teamMembers: { some: { userId: ctx.user.id } } },
        ],
      },
      include: {
        program: { select: { id: true, name: true, year: true, status: true } },
        files: {
          orderBy: { createdAt: 'desc' },
        },
        teamMembers: {
          include: {
            user: {
              select: { id: true, name: true, email: true, status: true },
            },
          },
          orderBy: { joinedAt: 'asc' },
        },
        submittedBy: {
          select: { id: true, name: true, email: true },
        },
        mentorAssignment: {
          include: {
            mentor: {
              select: { id: true, name: true, email: true },
            },
          },
        },
        wonAwards: {
          select: { id: true, name: true },
        },
      },
      orderBy: { createdAt: 'desc' },
    })

    if (!project) {
      return { project: null, openRounds: [], timeline: [], currentStatus: null }
    }

    const currentStatus = project.status ?? 'SUBMITTED'

    // Fetch status history
    const statusHistory = await ctx.prisma.projectStatusHistory.findMany({
      where: { projectId: project.id },
      orderBy: { changedAt: 'asc' },
      select: { status: true, changedAt: true },
    })

    const statusDateMap = new Map<string, Date>()
    for (const entry of statusHistory) {
      if (!statusDateMap.has(entry.status)) {
        statusDateMap.set(entry.status, entry.changedAt)
      }
    }

    const isRejected = currentStatus === 'REJECTED'
    const hasWonAward = project.wonAwards.length > 0

    // Build timeline
    const timeline = [
      {
        status: 'CREATED',
        label: 'Application Started',
        date: project.createdAt,
        completed: true,
        isTerminal: false,
      },
      {
        status: 'SUBMITTED',
        label: 'Application Submitted',
        date: project.submittedAt || statusDateMap.get('SUBMITTED') || null,
        completed: !!project.submittedAt || statusDateMap.has('SUBMITTED'),
        isTerminal: false,
      },
      {
        status: 'UNDER_REVIEW',
        label: 'Under Review',
        date: statusDateMap.get('ELIGIBLE') || statusDateMap.get('ASSIGNED') ||
          (currentStatus !== 'SUBMITTED' && project.submittedAt ? project.submittedAt : null),
        completed: ['ELIGIBLE', 'ASSIGNED', 'SEMIFINALIST', 'FINALIST', 'REJECTED'].includes(currentStatus),
        isTerminal: false,
      },
    ]

    if (isRejected) {
      timeline.push({
        status: 'REJECTED',
        label: 'Not Selected',
        date: statusDateMap.get('REJECTED') || null,
        completed: true,
        isTerminal: true,
      })
    } else {
      timeline.push(
        {
          status: 'SEMIFINALIST',
          label: 'Semi-finalist',
          date: statusDateMap.get('SEMIFINALIST') || null,
          completed: ['SEMIFINALIST', 'FINALIST'].includes(currentStatus) || hasWonAward,
          isTerminal: false,
        },
        {
          status: 'FINALIST',
          label: 'Finalist',
          date: statusDateMap.get('FINALIST') || null,
          completed: currentStatus === 'FINALIST' || hasWonAward,
          isTerminal: false,
        },
      )

      if (hasWonAward) {
        timeline.push({
          status: 'WINNER',
          label: `Winner${project.wonAwards.length > 0 ? ` - ${project.wonAwards[0].name}` : ''}`,
          date: null,
          completed: true,
          isTerminal: false,
        })
      }
    }

    const programId = project.programId
    const openRounds = programId
      ? await ctx.prisma.round.findMany({
          where: {
            competition: { programId },
            status: 'ROUND_ACTIVE',
          },
          orderBy: { sortOrder: 'asc' },
          select: {
            id: true,
            name: true,
            slug: true,
            windowCloseAt: true,
          },
        })
      : []

    // Determine user's role in the project
    const userMembership = project.teamMembers.find((tm) => tm.userId === ctx.user.id)
    const isTeamLead = project.submittedByUserId === ctx.user.id || userMembership?.role === 'LEAD'

    return {
      project: {
        ...project,
        isTeamLead,
        userRole: userMembership?.role || (project.submittedByUserId === ctx.user.id ? 'LEAD' : null),
      },
      openRounds,
      timeline,
      currentStatus,
    }
  }),
})
