import { randomUUID } from 'node:crypto';
import type { FastifyInstance } from 'fastify';
import { BadRequestError, ForbiddenError } from '../../lib/errors.js';
import {
  CreateSupportCaseSchema,
  CreateSupportEscalationSchema,
  CreateSupportNoteSchema,
  ListSupportCasesQuerySchema,
  SupportCaseDoc,
  SupportCaseNoteDoc,
  SupportEscalationEventDoc,
  UpdateSupportCaseSchema,
} from './types.js';
import * as repo from './repository.js';

function requireAdmin(req: { jwtPayload?: { sub?: string; role?: string; productId?: string } }): {
  userId: string;
  productId: string;
} {
  const payload = req.jwtPayload;
  if (!payload?.sub) throw new ForbiddenError('Authentication required');
  if (!payload.role || !['super_admin', 'admin'].includes(payload.role)) {
    throw new ForbiddenError('Admin access required');
  }
  return {
    userId: payload.sub,
    productId: payload.productId ?? process.env.DEFAULT_PRODUCT_ID ?? 'lysnrai',
  };
}

function validationError(message: string): never {
  throw new BadRequestError(message);
}

export async function supportCaseRoutes(app: FastifyInstance) {
  app.get('/support/cases', async req => {
    const access = requireAdmin(req);
    const parsed = ListSupportCasesQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.listCases(access.productId, parsed.data);
  });

  app.post('/support/cases', async req => {
    const access = requireAdmin(req);
    const parsed = CreateSupportCaseSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    const now = new Date().toISOString();
    const doc: SupportCaseDoc = {
      id: `sup_${randomUUID()}`,
      productId: access.productId,
      orgId: parsed.data.orgId,
      workspaceId: parsed.data.workspaceId,
      requesterUserId: parsed.data.requesterUserId,
      assignedTo: parsed.data.assignedTo,
      title: parsed.data.title,
      description: parsed.data.description,
      status: parsed.data.source === 'agent' ? 'triaged' : 'open',
      priority: parsed.data.priority,
      source: parsed.data.source,
      runId: parsed.data.runId,
      reviewId: parsed.data.reviewId,
      knowledgeBaseId: parsed.data.knowledgeBaseId,
      tags: parsed.data.tags,
      createdAt: now,
      updatedAt: now,
    };
    return repo.createCase(doc);
  });

  app.get('/support/cases/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    return repo.getCase(id, access.productId);
  });

  app.patch('/support/cases/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const parsed = UpdateSupportCaseSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.updateCase(id, access.productId, parsed.data);
  });

  app.get('/support/cases/:id/notes', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };
    return repo.listNotes(id);
  });

  app.post('/support/cases/:id/notes', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    await repo.getCase(id, access.productId);
    const parsed = CreateSupportNoteSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    const doc: SupportCaseNoteDoc = {
      id: `${id}:note:${randomUUID()}`,
      caseId: id,
      productId: access.productId,
      authorType: parsed.data.authorType,
      authorId: parsed.data.authorId,
      visibility: parsed.data.visibility,
      body: parsed.data.body,
      createdAt: new Date().toISOString(),
    };
    return repo.createNote(doc);
  });

  app.get('/support/cases/:id/escalations', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };
    return repo.listEscalations(id);
  });

  app.post('/support/cases/:id/escalations', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    await repo.getCase(id, access.productId);
    const parsed = CreateSupportEscalationSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    const event: SupportEscalationEventDoc = {
      id: `${id}:esc:${randomUUID()}`,
      caseId: id,
      productId: access.productId,
      escalatedTo: parsed.data.escalatedTo,
      reason: parsed.data.reason,
      triggeredBy: access.userId,
      createdAt: new Date().toISOString(),
    };
    await repo.updateCase(id, access.productId, { status: 'escalated' });
    return repo.createEscalation(event);
  });

  // ── Case Timeline ───────────────────────────────────────

  app.get('/support/cases/:id/timeline', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const supportCase = await repo.getCase(id, access.productId);

    const [notes, escalations] = await Promise.all([repo.listNotes(id), repo.listEscalations(id)]);

    const events: Array<{
      type: string;
      timestamp: string;
      actor?: string;
      details: Record<string, unknown>;
    }> = [];

    events.push({
      type: 'case_created',
      timestamp: supportCase.createdAt,
      actor: supportCase.requesterUserId,
      details: {
        title: supportCase.title,
        priority: supportCase.priority,
        source: supportCase.source,
      },
    });

    for (const note of notes) {
      events.push({
        type: note.visibility === 'internal' ? 'internal_note' : 'customer_note',
        timestamp: note.createdAt,
        actor: note.authorId,
        details: { body: note.body, authorType: note.authorType },
      });
    }

    for (const esc of escalations) {
      events.push({
        type: 'escalation',
        timestamp: esc.createdAt,
        actor: esc.triggeredBy,
        details: { escalatedTo: esc.escalatedTo, reason: esc.reason },
      });
    }

    events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));

    return { caseId: id, events };
  });

  // ── SLA Engine ──────────────────────────────────────────

  const SLA_TARGETS_HOURS: Record<string, { responseHours: number; resolutionHours: number }> = {
    critical: { responseHours: 1, resolutionHours: 4 },
    high: { responseHours: 4, resolutionHours: 24 },
    medium: { responseHours: 8, resolutionHours: 72 },
    low: { responseHours: 24, resolutionHours: 168 },
  };

  app.get('/support/cases/:id/sla', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const supportCase = await repo.getCase(id, access.productId);
    const notes = await repo.listNotes(id);

    const target = SLA_TARGETS_HOURS[supportCase.priority] ?? SLA_TARGETS_HOURS['medium'];
    const createdMs = new Date(supportCase.createdAt).getTime();
    const nowMs = Date.now();

    // First response = first note by a user (not system)
    const firstResponse = notes.find(n => n.authorType === 'user');
    const responseMs = firstResponse
      ? new Date(firstResponse.createdAt).getTime() - createdMs
      : nowMs - createdMs;

    const isResolved = ['resolved', 'closed'].includes(supportCase.status);
    const resolutionMs = isResolved
      ? new Date(supportCase.updatedAt).getTime() - createdMs
      : nowMs - createdMs;

    const responseBreached = responseMs > target.responseHours * 3600000;
    const resolutionBreached = !isResolved && resolutionMs > target.resolutionHours * 3600000;

    return {
      caseId: id,
      priority: supportCase.priority,
      target,
      response: {
        elapsedHours: Math.round((responseMs / 3600000) * 10) / 10,
        targetHours: target.responseHours,
        breached: responseBreached,
        hasResponse: !!firstResponse,
      },
      resolution: {
        elapsedHours: Math.round((resolutionMs / 3600000) * 10) / 10,
        targetHours: target.resolutionHours,
        breached: resolutionBreached,
        isResolved,
      },
    };
  });

  // ── Auto-Triage ─────────────────────────────────────────

  app.post('/support/cases/:id/auto-triage', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const supportCase = await repo.getCase(id, access.productId);

    // Simple keyword-based auto-triage
    const text = `${supportCase.title} ${supportCase.description ?? ''}`.toLowerCase();

    let suggestedPriority: string = supportCase.priority;
    const tags = [...(supportCase.tags ?? [])];

    // Priority heuristics
    if (text.includes('outage') || text.includes('production down') || text.includes('data loss')) {
      suggestedPriority = 'critical';
      tags.push('auto-triaged:critical');
    } else if (text.includes('error') || text.includes('broken') || text.includes('failing')) {
      suggestedPriority = 'high';
      tags.push('auto-triaged:high');
    } else if (text.includes('slow') || text.includes('performance') || text.includes('timeout')) {
      suggestedPriority = 'medium';
      tags.push('auto-triaged:medium');
    } else if (text.includes('feature') || text.includes('request') || text.includes('question')) {
      suggestedPriority = 'low';
      tags.push('auto-triaged:low');
    }

    // Category detection
    if (text.includes('auth') || text.includes('login') || text.includes('password')) {
      tags.push('category:auth');
    }
    if (text.includes('billing') || text.includes('payment') || text.includes('invoice')) {
      tags.push('category:billing');
    }
    if (text.includes('api') || text.includes('endpoint') || text.includes('webhook')) {
      tags.push('category:api');
    }

    const dedupedTags = [...new Set(tags)];
    // Only advance to 'triaged' if still in 'open' state — don't regress later statuses
    const triageableStatuses = ['open'];
    const statusUpdate = triageableStatuses.includes(supportCase.status)
      ? 'triaged'
      : supportCase.status;
    const updated = await repo.updateCase(id, access.productId, {
      priority: suggestedPriority as SupportCaseDoc['priority'],
      status: statusUpdate,
      tags: dedupedTags,
    });

    return {
      triaged: true,
      previousPriority: supportCase.priority,
      newPriority: suggestedPriority,
      tagsAdded: dedupedTags.filter(t => !(supportCase.tags ?? []).includes(t)),
      case: updated,
    };
  });

  // ── Case Metrics ────────────────────────────────────────

  app.get('/support/metrics', async req => {
    const access = requireAdmin(req);
    const allCases = await repo.listAllCases(access.productId);

    const byStatus: Record<string, number> = {};
    const byPriority: Record<string, number> = {};
    const bySource: Record<string, number> = {};
    let totalResolutionMs = 0;
    let resolvedCount = 0;
    let slaBreachCount = 0;

    for (const c of allCases) {
      byStatus[c.status] = (byStatus[c.status] ?? 0) + 1;
      byPriority[c.priority] = (byPriority[c.priority] ?? 0) + 1;
      bySource[c.source] = (bySource[c.source] ?? 0) + 1;

      if (['resolved', 'closed'].includes(c.status)) {
        const created = new Date(c.createdAt).getTime();
        const resolved = new Date(c.updatedAt).getTime();
        if (resolved > created) {
          totalResolutionMs += resolved - created;
          resolvedCount++;

          const target = SLA_TARGETS_HOURS[c.priority] ?? SLA_TARGETS_HOURS['medium'];
          if (resolved - created > target.resolutionHours * 3600000) {
            slaBreachCount++;
          }
        }
      }
    }

    const avgResolutionMs = resolvedCount > 0 ? Math.round(totalResolutionMs / resolvedCount) : 0;
    const openCount = allCases.filter(c => !['resolved', 'closed'].includes(c.status)).length;

    return {
      total: allCases.length,
      open: openCount,
      resolved: resolvedCount,
      byStatus,
      byPriority,
      bySource,
      avgResolutionHours: Math.round((avgResolutionMs / 3600000) * 10) / 10,
      slaBreachCount,
      slaComplianceRate:
        resolvedCount > 0
          ? Math.round(((resolvedCount - slaBreachCount) / resolvedCount) * 100)
          : 100,
    };
  });
}
