import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, adminProcedure, protectedProcedure } from '../trpc'
import { prisma } from '@/lib/prisma'
import { logAudit } from '../utils/audit'
import {
  tagProject,
  tagProjectsBatch,
  getTagSuggestions,
  addProjectTag,
  removeProjectTag,
  getTaggingSettings,
  getAvailableTags,
} from '../services/ai-tagging'
import {
  createNotification,
  notifyAdmins,
  NotificationTypes,
} from '../services/in-app-notification'

// Background job runner for tagging — uses batched API calls for efficiency
async function runTaggingJob(jobId: string, userId: string) {
  const job = await prisma.taggingJob.findUnique({
    where: { id: jobId },
  })

  if (!job) {
    console.error(`[AI Tagging Job] Job not found: ${jobId}`)
    return
  }

  console.log(`[AI Tagging Job] Starting job ${jobId} (batched mode)...`)

  // Mark as running
  await prisma.taggingJob.update({
    where: { id: jobId },
    data: { status: 'RUNNING', startedAt: new Date() },
  })

  try {
    // Get settings and tags
    const settings = await getTaggingSettings()
    if (!settings.enabled) {
      throw new Error('AI tagging is not enabled')
    }

    const availableTags = await getAvailableTags()
    if (availableTags.length === 0) {
      throw new Error('No expertise tags configured')
    }

    // Get projects to tag (always by programId)
    if (!job.programId) {
      throw new Error('Job must have a programId')
    }
    const whereClause = { programId: job.programId }

    const allProjects = await prisma.project.findMany({
      where: whereClause,
      select: { id: true, title: true, tags: true, projectTags: { select: { tagId: true } } },
    })

    const untaggedProjects = allProjects.filter(p => p.tags.length === 0)
    const skippedCount = allProjects.length - untaggedProjects.length

    console.log(`[AI Tagging Job] Found ${untaggedProjects.length} untagged projects (${skippedCount} already tagged)`)

    await prisma.taggingJob.update({
      where: { id: jobId },
      data: {
        totalProjects: untaggedProjects.length,
        skippedCount,
      },
    })

    if (untaggedProjects.length === 0) {
      await prisma.taggingJob.update({
        where: { id: jobId },
        data: {
          status: 'COMPLETED',
          completedAt: new Date(),
        },
      })
      return
    }

    const startTime = Date.now()

    // Use batched tagging — processes 10 projects per API call, 3 concurrent calls
    const { results, totalTokens } = await tagProjectsBatch(
      untaggedProjects,
      userId,
      async (processed, total) => {
        // Update job progress on each batch completion
        const taggedSoFar = results?.length ?? processed
        await prisma.taggingJob.update({
          where: { id: jobId },
          data: {
            processedCount: processed,
            taggedCount: taggedSoFar,
          },
        })

        const elapsed = ((Date.now() - startTime) / 1000).toFixed(0)
        console.log(`[AI Tagging Job] Progress: ${processed}/${total} (${elapsed}s elapsed)`)
      }
    )

    const taggedCount = results.filter(r => r.applied.length > 0).length
    const failedCount = untaggedProjects.length - results.length

    const totalTime = ((Date.now() - startTime) / 1000).toFixed(1)
    console.log(`[AI Tagging Job] Complete: ${taggedCount} tagged, ${failedCount} failed in ${totalTime}s (${totalTokens} tokens)`)

    // Mark as completed
    await prisma.taggingJob.update({
      where: { id: jobId },
      data: {
        status: 'COMPLETED',
        completedAt: new Date(),
        processedCount: results.length,
        taggedCount,
        failedCount,
      },
    })

    // Send notification to admins
    await notifyAdmins({
      type: NotificationTypes.AI_SUGGESTIONS_READY,
      title: 'AI Tagging Complete',
      message: `Tagged ${taggedCount} projects${failedCount > 0 ? `, ${failedCount} failed` : ''}${skippedCount > 0 ? `, ${skippedCount} already had tags` : ''}.`,
      linkUrl: '/admin/projects',
      linkLabel: 'View Projects',
      priority: 'normal',
      metadata: { jobId, taggedCount, failedCount, skippedCount, totalTokens },
    })

  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : 'Unknown error'
    console.error(`[AI Tagging Job] Job failed: ${errorMsg}`)

    await prisma.taggingJob.update({
      where: { id: jobId },
      data: {
        status: 'FAILED',
        completedAt: new Date(),
        errorMessage: errorMsg,
      },
    })

    // Notify about failure
    await notifyAdmins({
      type: NotificationTypes.SYSTEM_ERROR,
      title: 'AI Tagging Failed',
      message: errorMsg,
      linkUrl: '/admin/projects',
      priority: 'high',
    })
  }
}

export const tagRouter = router({
  /**
   * List all expertise tags
   */
  list: protectedProcedure
    .input(
      z.object({
        category: z.string().optional(),
        isActive: z.boolean().optional(),
        includeUsageCount: z.boolean().default(false),
      })
    )
    .query(async ({ ctx, input }) => {
      const where: Record<string, unknown> = {}

      if (input.category) {
        where.category = input.category
      }
      if (input.isActive !== undefined) {
        where.isActive = input.isActive
      }

      const tags = await ctx.prisma.expertiseTag.findMany({
        where,
        orderBy: [{ category: 'asc' }, { sortOrder: 'asc' }, { name: 'asc' }],
      })

      if (!input.includeUsageCount) {
        return { tags }
      }

      // Count usage for each tag
      const tagsWithCounts = await Promise.all(
        tags.map(async (tag) => {
          const userCount = await ctx.prisma.user.count({
            where: {
              expertiseTags: { has: tag.name },
            },
          })
          const projectCount = await ctx.prisma.project.count({
            where: {
              tags: { has: tag.name },
            },
          })
          return {
            ...tag,
            userCount,
            projectCount,
            totalUsage: userCount + projectCount,
          }
        })
      )

      return { tags: tagsWithCounts }
    }),

  /**
   * Get a single tag by ID
   */
  get: adminProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const tag = await ctx.prisma.expertiseTag.findUniqueOrThrow({
        where: { id: input.id },
      })

      // Get usage counts
      const [userCount, projectCount] = await Promise.all([
        ctx.prisma.user.count({
          where: { expertiseTags: { has: tag.name } },
        }),
        ctx.prisma.project.count({
          where: { tags: { has: tag.name } },
        }),
      ])

      return {
        ...tag,
        userCount,
        projectCount,
      }
    }),

  /**
   * Get all unique categories
   */
  getCategories: protectedProcedure.query(async ({ ctx }) => {
    const tags = await ctx.prisma.expertiseTag.findMany({
      where: { isActive: true },
      select: { category: true },
      distinct: ['category'],
    })

    return tags
      .map((t) => t.category)
      .filter((c): c is string => c !== null)
      .sort()
  }),

  /**
   * Create a new expertise tag (admin only)
   */
  create: adminProcedure
    .input(
      z.object({
        name: z.string().min(1).max(100),
        description: z.string().optional(),
        category: z.string().optional(),
        color: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
        sortOrder: z.number().int().default(0),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Check if tag name already exists
      const existing = await ctx.prisma.expertiseTag.findUnique({
        where: { name: input.name },
      })

      if (existing) {
        throw new TRPCError({
          code: 'CONFLICT',
          message: 'A tag with this name already exists',
        })
      }

      const tag = await ctx.prisma.expertiseTag.create({
        data: input,
      })

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

      return tag
    }),

  /**
   * Update an expertise tag (admin only)
   */
  update: adminProcedure
    .input(
      z.object({
        id: z.string(),
        name: z.string().min(1).max(100).optional(),
        description: z.string().optional().nullable(),
        category: z.string().optional().nullable(),
        color: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().nullable(),
        isActive: z.boolean().optional(),
        sortOrder: z.number().int().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id, ...data } = input

      // If name is being changed, check for conflicts
      if (data.name) {
        const existing = await ctx.prisma.expertiseTag.findFirst({
          where: {
            name: data.name,
            id: { not: id },
          },
        })

        if (existing) {
          throw new TRPCError({
            code: 'CONFLICT',
            message: 'A tag with this name already exists',
          })
        }
      }

      // Get old tag for name change handling
      const oldTag = await ctx.prisma.expertiseTag.findUniqueOrThrow({
        where: { id },
      })

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

      // If name changed, update all users and projects with this tag
      if (data.name && data.name !== oldTag.name) {
        const newName = data.name

        // Update users
        const usersWithTag = await ctx.prisma.user.findMany({
          where: { expertiseTags: { has: oldTag.name } },
          select: { id: true, expertiseTags: true },
        })

        for (const user of usersWithTag) {
          await ctx.prisma.user.update({
            where: { id: user.id },
            data: {
              expertiseTags: user.expertiseTags.map((t) =>
                t === oldTag.name ? newName : t
              ),
            },
          })
        }

        // Update projects
        const projectsWithTag = await ctx.prisma.project.findMany({
          where: { tags: { has: oldTag.name } },
          select: { id: true, tags: true },
        })

        for (const project of projectsWithTag) {
          await ctx.prisma.project.update({
            where: { id: project.id },
            data: {
              tags: project.tags.map((t) =>
                t === oldTag.name ? newName : t
              ),
            },
          })
        }
      }

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

      return tag
    }),

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

      // Remove tag from all users
      const usersWithTag = await ctx.prisma.user.findMany({
        where: { expertiseTags: { has: tag.name } },
        select: { id: true, expertiseTags: true },
      })

      for (const user of usersWithTag) {
        await ctx.prisma.user.update({
          where: { id: user.id },
          data: {
            expertiseTags: user.expertiseTags.filter((t) => t !== tag.name),
          },
        })
      }

      // Remove tag from all projects
      const projectsWithTag = await ctx.prisma.project.findMany({
        where: { tags: { has: tag.name } },
        select: { id: true, tags: true },
      })

      for (const project of projectsWithTag) {
        await ctx.prisma.project.update({
          where: { id: project.id },
          data: {
            tags: project.tags.filter((t) => t !== tag.name),
          },
        })
      }

      // Delete the tag
      await ctx.prisma.expertiseTag.delete({
        where: { id: input.id },
      })

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

      return tag
    }),

  /**
   * Bulk create tags (admin only)
   */
  bulkCreate: adminProcedure
    .input(
      z.object({
        tags: z.array(
          z.object({
            name: z.string().min(1).max(100),
            description: z.string().optional(),
            category: z.string().optional(),
            color: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
          })
        ),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Get existing tag names
      const existingTags = await ctx.prisma.expertiseTag.findMany({
        where: { name: { in: input.tags.map((t) => t.name) } },
        select: { name: true },
      })
      const existingNames = new Set(existingTags.map((t) => t.name))

      // Filter out existing tags
      const newTags = input.tags.filter((t) => !existingNames.has(t.name))

      if (newTags.length === 0) {
        return { created: 0, skipped: input.tags.length }
      }

      // Get max sort order
      const maxOrder = await ctx.prisma.expertiseTag.aggregate({
        _max: { sortOrder: true },
      })
      const startOrder = (maxOrder._max.sortOrder || 0) + 1

      const created = await ctx.prisma.expertiseTag.createMany({
        data: newTags.map((t, i) => ({
          ...t,
          sortOrder: startOrder + i,
        })),
      })

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'BULK_CREATE',
        entityType: 'ExpertiseTag',
        detailsJson: { count: created.count, skipped: existingNames.size },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return { created: created.count, skipped: existingNames.size }
    }),

  /**
   * Reorder tags (admin only)
   */
  reorder: adminProcedure
    .input(
      z.object({
        items: z.array(
          z.object({
            id: z.string(),
            sortOrder: z.number().int(),
          })
        ),
      })
    )
    .mutation(async ({ ctx, input }) => {
      await ctx.prisma.$transaction(
        input.items.map((item) =>
          ctx.prisma.expertiseTag.update({
            where: { id: item.id },
            data: { sortOrder: item.sortOrder },
          })
        )
      )

      return { success: true }
    }),

  // ═══════════════════════════════════════════════════════════════════════════
  // PROJECT TAGGING (AI-powered)
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Get tags for a project
   */
  getProjectTags: protectedProcedure
    .input(z.object({ projectId: z.string() }))
    .query(async ({ ctx, input }) => {
      const tags = await ctx.prisma.projectTag.findMany({
        where: { projectId: input.projectId },
        include: { tag: true },
        orderBy: { confidence: 'desc' },
      })

      return tags.map((pt) => ({
        id: pt.id,
        tagId: pt.tagId,
        name: pt.tag.name,
        category: pt.tag.category,
        color: pt.tag.color,
        confidence: pt.confidence,
        source: pt.source,
        createdAt: pt.createdAt,
      }))
    }),

  /**
   * Get AI tag suggestions for a project (without applying)
   */
  getSuggestions: adminProcedure
    .input(z.object({ projectId: z.string() }))
    .query(async ({ ctx, input }) => {
      const suggestions = await getTagSuggestions(input.projectId, ctx.user.id)
      return suggestions
    }),

  /**
   * Tag a single project with AI
   */
  tagProject: adminProcedure
    .input(z.object({ projectId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const result = await tagProject(input.projectId, ctx.user.id)

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'AI_TAG',
        entityType: 'Project',
        entityId: input.projectId,
        detailsJson: {
          applied: result.applied.map((t) => t.tagName),
          tokensUsed: result.tokensUsed,
        },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return result
    }),

  /**
   * Start a background tagging job for a round
   */
  startTaggingJob: adminProcedure
    .input(z.object({
      programId: z.string(),
    }))
    .mutation(async ({ ctx, input }) => {
      // Check for existing running job
      const existingJob = await ctx.prisma.taggingJob.findFirst({
        where: {
          programId: input.programId,
          status: { in: ['PENDING', 'RUNNING'] },
        },
      })

      if (existingJob) {
        throw new TRPCError({
          code: 'CONFLICT',
          message: 'A tagging job is already running',
        })
      }

      // Create the job
      const job = await ctx.prisma.taggingJob.create({
        data: {
          programId: input.programId,
          status: 'PENDING',
        },
      })

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

      // Start job in background (don't await)
      runTaggingJob(job.id, ctx.user.id).catch((error) => {
        console.error('[AI Tagging] Background job error:', error)
      })

      return { jobId: job.id }
    }),

  /**
   * Get tagging job status
   */
  getTaggingJobStatus: adminProcedure
    .input(z.object({ jobId: z.string() }))
    .query(async ({ ctx, input }) => {
      const job = await ctx.prisma.taggingJob.findUnique({
        where: { id: input.jobId },
      })

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

      return {
        id: job.id,
        status: job.status,
        totalProjects: job.totalProjects,
        processedCount: job.processedCount,
        taggedCount: job.taggedCount,
        skippedCount: job.skippedCount,
        failedCount: job.failedCount,
        errorMessage: job.errorMessage,
        errors: job.errorsJson as string[] | null,
        startedAt: job.startedAt,
        completedAt: job.completedAt,
      }
    }),

  /**
   * Get latest tagging job for a round or program
   */
  getLatestTaggingJob: adminProcedure
    .input(z.object({
      programId: z.string(),
    }))
    .query(async ({ ctx, input }) => {
      const job = await ctx.prisma.taggingJob.findFirst({
        where: {
          programId: input.programId,
        },
        orderBy: { createdAt: 'desc' },
      })

      if (!job) {
        return null
      }

      return {
        id: job.id,
        status: job.status,
        totalProjects: job.totalProjects,
        processedCount: job.processedCount,
        taggedCount: job.taggedCount,
        skippedCount: job.skippedCount,
        failedCount: job.failedCount,
        errorMessage: job.errorMessage,
        errors: job.errorsJson as string[] | null,
        startedAt: job.startedAt,
        completedAt: job.completedAt,
        createdAt: job.createdAt,
      }
    }),

  /**
   * Manually add a tag to a project
   */
  addProjectTag: adminProcedure
    .input(
      z.object({
        projectId: z.string(),
        tagId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      await addProjectTag(input.projectId, input.tagId)

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'ADD_TAG',
        entityType: 'Project',
        entityId: input.projectId,
        detailsJson: { tagId: input.tagId },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return { success: true }
    }),

  /**
   * Remove a tag from a project
   */
  removeProjectTag: adminProcedure
    .input(
      z.object({
        projectId: z.string(),
        tagId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      await removeProjectTag(input.projectId, input.tagId)

      // Audit log
      await logAudit({
        prisma: ctx.prisma,
        userId: ctx.user.id,
        action: 'REMOVE_TAG',
        entityType: 'Project',
        entityId: input.projectId,
        detailsJson: { tagId: input.tagId },
        ipAddress: ctx.ip,
        userAgent: ctx.userAgent,
      })

      return { success: true }
    }),
})
