import type { FastifyInstance } from 'fastify';
import { requireJwtOrApiKey } from '../../lib/api-key-auth.js';
import { BadRequestError } from '../../lib/errors.js';
import { CreateExportSchema } from './types.js';
import type { ExportJobDoc, ExportType } from './types.js';
import * as repo from './repository.js';
import * as authRepo from '../auth/repository.js';
import * as subsRepo from '../subscriptions/repository.js';
import * as auditRepo from '../audit/repository.js';
import * as telemetryRepo from '../telemetry/repository.js';
import * as usageRepo from '../usage/repository.js';
import * as licensesRepo from '../licenses/repository.js';

export async function exportRoutes(app: FastifyInstance) {
  function requireExportRead(req: import('fastify').FastifyRequest) {
    return requireJwtOrApiKey(req, {
      jwtRoles: ['super_admin', 'admin'],
      apiKeyScopes: ['exports:read'],
      apiKeyTokenTypes: ['service_api'],
      rateLimitKey: 'exports:read',
    });
  }

  function requireExportWrite(req: import('fastify').FastifyRequest) {
    return requireJwtOrApiKey(req, {
      jwtRoles: ['super_admin', 'admin'],
      apiKeyScopes: ['exports:write'],
      apiKeyTokenTypes: ['service_api'],
      rateLimitKey: 'exports:write',
    });
  }

  // Start a new export job
  app.post('/exports', async (req, reply) => {
    const access = await requireExportWrite(req);
    const parsed = CreateExportSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const now = new Date().toISOString();
    const expiresAt = new Date(Date.now() + 7 * 86400000).toISOString(); // 7 days

    const job: ExportJobDoc = {
      id: `exp_${crypto.randomUUID()}`,
      productId: access.productId,
      type: parsed.data.type,
      format: parsed.data.format,
      filters: parsed.data.filters,
      status: 'pending',
      requestedBy: access.actorId,
      expiresAt,
      createdAt: now,
    };

    const created = await repo.createExportJob(job);

    req.log.info(
      { exportId: created.id, type: created.type, format: created.format },
      '[exports] Export job created — processing async'
    );

    // Process async — respond immediately, update job status when done
    const log = req.log;
    process.nextTick(async () => {
      let currentJob = created;
      try {
        currentJob = await repo.updateExportJob({
          ...currentJob,
          status: 'processing',
          startedAt: new Date().toISOString(),
        });
        const rows = await fetchExportData(created.type, access.productId, created.filters);
        const serialized = created.format === 'json' ? JSON.stringify(rows, null, 2) : toCsv(rows);
        const fileName = `${created.type}-${access.productId}-${Date.now()}.${created.format}`;
        currentJob = await repo.updateExportJob({
          ...currentJob,
          status: 'ready',
          data: serialized,
          rowCount: rows.length,
          fileSizeBytes: Buffer.byteLength(serialized, 'utf8'),
          fileName,
          completedAt: new Date().toISOString(),
        });
        log.info({ exportId: created.id, rows: rows.length }, '[exports] Export job completed');
      } catch (err) {
        log.error({ err, exportId: created.id }, '[exports] Export job failed');
        await repo
          .updateExportJob({
            ...currentJob,
            status: 'failed',
            error: err instanceof Error ? err.message : 'Unknown error',
            completedAt: new Date().toISOString(),
          })
          .catch(() => {});
      }
    });

    return reply.status(201).send(created);
  });

  // List export jobs (metadata only — excludes data payload)
  app.get('/exports', async req => {
    const access = await requireExportRead(req);
    const query = req.query as Record<string, string>;
    const limit = query.limit ? parseInt(query.limit, 10) : 20;
    const jobs = await repo.listExportJobs(access.productId, Math.min(limit, 100));
    const stripped = jobs.map(j => {
      const meta = { ...j };
      delete meta.data;
      return meta;
    });
    return { exports: stripped, count: stripped.length };
  });

  // Get a specific export job (metadata only, no data payload)
  app.get('/exports/:id', async req => {
    const access = await requireExportRead(req);
    const { id } = req.params as { id: string };
    const job = await repo.getExportJob(id, access.productId);
    if (!job) throw new BadRequestError('Export job not found');
    const meta = { ...job };
    delete meta.data;
    return meta;
  });

  // Download export data
  app.get('/exports/:id/download', async (req, reply) => {
    const access = await requireExportRead(req);
    const { id } = req.params as { id: string };
    const job = await repo.getExportJob(id, access.productId);
    if (!job) throw new BadRequestError('Export job not found');
    if (job.status !== 'ready' || !job.data) {
      throw new BadRequestError('Export is not ready for download');
    }
    const contentType = job.format === 'json' ? 'application/json' : 'text/csv';
    return reply
      .header('Content-Type', contentType)
      .header('Content-Disposition', `attachment; filename="${job.fileName || 'export'}"`)
      .send(job.data);
  });
}

// ── Helpers ──────────────────────────────────────────────────

async function fetchExportData(
  type: ExportType,
  productId: string,
  filters: Record<string, unknown>
): Promise<Record<string, unknown>[]> {
  const limit = typeof filters.limit === 'number' ? Math.min(filters.limit, 5000) : 1000;

  switch (type) {
    case 'users': {
      const users = await authRepo.list(productId, limit);
      return users.map(u => ({
        id: u.id,
        email: u.email,
        displayName: u.displayName,
        plan: u.plan,
        role: u.role,
        status: u.status,
        emailVerified: u.emailVerified,
        createdAt: u.createdAt,
        lastLoginAt: u.lastLoginAt,
      }));
    }
    case 'subscriptions': {
      const users = await authRepo.list(productId, limit);
      const subs = await Promise.all(users.map(u => subsRepo.getByUserId(u.id, productId)));
      return subs.filter(Boolean).map(s => ({ ...s })) as Record<string, unknown>[];
    }
    case 'audit': {
      const days = typeof filters.days === 'number' ? filters.days : 90;
      const rows = await auditRepo.query({ days, limit, offset: 0 }, productId);
      return rows.map(r => ({ ...r }));
    }
    case 'telemetry': {
      const result = await telemetryRepo.queryEvents(productId, {
        limit,
        from: typeof filters.from === 'string' ? filters.from : undefined,
        to: typeof filters.to === 'string' ? filters.to : undefined,
      });
      return result.events.map(e => ({ ...e }));
    }
    case 'usage': {
      const rows = await usageRepo.list({ productId, limit });
      return rows.map(r => ({ ...r }));
    }
    case 'licenses': {
      const users = await authRepo.list(productId, 200);
      const all = await Promise.all(users.map(u => licensesRepo.getByUserId(u.id, productId)));
      return all.flat().map(l => ({ ...l }));
    }
    default:
      return [];
  }
}

function toCsv(rows: Record<string, unknown>[]): string {
  if (rows.length === 0) return '';
  const headers = Object.keys(rows[0]);
  const lines = [headers.join(',')];
  for (const row of rows) {
    lines.push(
      headers
        .map(h => {
          const val = row[h];
          const str = val === null || val === undefined ? '' : String(val);
          return str.includes(',') || str.includes('"') || str.includes('\n')
            ? `"${str.replace(/"/g, '""')}"`
            : str;
        })
        .join(',')
    );
  }
  return lines.join('\n');
}
