import type { FastifyInstance, FastifySchema } from 'fastify';
import type pg from 'pg';
import type { Authenticate } from '../../../shared/auth.js';
import { AppError } from '../../../shared/errors.js';
import { parseJson } from '../../../shared/http-input.js';
import { errorSchema } from '../../../shared/swagger.js';
import {
  REPORTING_FILTER_META,
  comparisonRange,
  reportingFiltersSchema,
} from '../application/filters.js';
import { ReportingService } from '../application/reporting-service.js';
import { requireReportingPermission, REPORTING_ROLE_PERMISSIONS, userReportingPermissions } from '../domain/permissions.js';
import type { ReportingPermission } from '../domain/permissions.js';

export interface ReportingRoutesDeps {
  authenticate: Authenticate;
  pool: pg.Pool;
}

/**
 * Reporting routes (F-143, F-146).
 *
 * F-143: filter contracts + RBAC foundation.
 * F-146: ReportingService with summary and sales endpoints.
 *
 * `ReportingFilterMeta.dataAvailability` is metadata only — it never converts
 * an unavailable metric into a value.
 */
export async function registerReportingRoutes(
  app: FastifyInstance,
  deps: ReportingRoutesDeps,
): Promise<void> {
  const reporting = new ReportingService(deps.pool);

  const filtersSchemaRoute: FastifySchema = {
    tags: ['Reporting'],
    summary: 'Reporting filter schema + RBAC contract',
    description:
      "Returns the shared reporting filter schema, comparison contract, data-availability metadata and the caller's reporting permissions. Requires REPORTING_VIEW.",
    querystring: { type: 'object' },
    response: { 401: errorSchema, 403: errorSchema },
  };
  // No 200 response schema: the DTO is dynamic metadata; let it pass through
  // unmodified (matches security.routes.ts pattern, avoids field stripping).
  app.get('/reporting/filters/schema', { schema: filtersSchemaRoute }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireReportingPermission(user, 'REPORTING_VIEW');
    return reply.send({
      filterSchema: REPORTING_FILTER_META,
      comparison: REPORTING_FILTER_META.comparison,
      dataAvailability: REPORTING_FILTER_META.dataAvailability,
      permissions: {
        role: user.role,
        grants: userReportingPermissions(user),
      },
    });
  });

  const validateSchemaRoute: FastifySchema = {
    tags: ['Reporting'],
    summary: 'Validate reporting filters',
    description:
      'Parses, validates and normalizes a reporting filter query and computes the comparison range. Requires REPORTING_SALES.',
    querystring: { type: 'object' },
    response: { 400: errorSchema, 401: errorSchema, 403: errorSchema },
  };
  app.get('/reporting/filters/validate', { schema: validateSchemaRoute }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireReportingPermission(user, 'REPORTING_SALES');
    const filters = parseJson(reportingFiltersSchema, request.query ?? {});
    const comparison = comparisonRange(filters.range, filters.compare);
    return reply.send({ ok: true, filters, comparison: { range: comparison } });
  });

  // ── F-146: Summary and Sales ──────────────────────────────────────────────

  const summarySchema: FastifySchema = {
    tags: ['Reporting'],
    summary: 'Reporting summary',
    description:
      'Aggregated metrics (orders, customers, grossSales, discounts, tax, units) for the given date range and filters. Requires REPORTING_SALES.',
    querystring: { type: 'object' },
    response: { 400: errorSchema, 401: errorSchema, 403: errorSchema },
  };
  app.get('/reporting/summary', { schema: summarySchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireReportingPermission(user, 'REPORTING_SALES');
    try {
      const filters = parseJson(reportingFiltersSchema, request.query ?? {});
      return reply.send(await reporting.summary(filters));
    } catch (err) {
      if (err instanceof Error && err.name === 'ZodError') {
        throw new AppError(400, 'INVALID_FILTERS', (err as Error).message);
      }
      throw err;
    }
  });

  const salesSchema: FastifySchema = {
    tags: ['Reporting'],
    summary: 'Reporting sales (grouped)',
    description:
      'Grouped sales rows with pagination. groupBy=day|week|month|hour|store|channel|terminal. Requires REPORTING_SALES.',
    querystring: { type: 'object' },
    response: { 400: errorSchema, 401: errorSchema, 403: errorSchema },
  };
  app.get('/reporting/sales', { schema: salesSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireReportingPermission(user, 'REPORTING_SALES');
    try {
      const filters = parseJson(reportingFiltersSchema, request.query ?? {});
      return reply.send(await reporting.sales(filters));
    } catch (err) {
      if (err instanceof Error && err.name === 'ZodError') {
        throw new AppError(400, 'INVALID_FILTERS', (err as Error).message);
      }
      throw err;
    }
  });

  // ── F-149: Products ranking ───────────────────────────────────────────────

  const productsSchema: FastifySchema = {
    tags: ['Reporting'],
    summary: 'Reporting product rankings',
    description:
      'Top products by units sold or revenue with category/brand. Requires REPORTING_PRODUCTS.',
    querystring: { type: 'object' },
    response: { 400: errorSchema, 401: errorSchema, 403: errorSchema },
  };
  app.get('/reporting/products', { schema: productsSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireReportingPermission(user, 'REPORTING_PRODUCTS');
    try {
      const filters = parseJson(reportingFiltersSchema, request.query ?? {});
      return reply.send(await reporting.products(filters));
    } catch (err) {
      if (err instanceof Error && err.name === 'ZodError') {
        throw new AppError(400, 'INVALID_FILTERS', (err as Error).message);
      }
      throw err;
    }
  });

  // ── F-150: CSV export ─────────────────────────────────────────────────


  app.get<{ Params: { report: string } }>(
    '/reporting/export/:report',
    {
      schema: {
        tags: ['Reporting'],
        summary: 'Export reporting data as CSV',
        description: 'Streams a filtered dataset as CSV. Requires REPORTING_EXPORT.',
        params: {
          type: 'object',
          properties: {
            report: { type: 'string', enum: ['summary', 'sales', 'products'] },
          },
        },
        querystring: { type: 'object' },
        response: { 400: errorSchema, 401: errorSchema, 403: errorSchema },
      },
    },
    async (request, reply) => {
      const user = await deps.authenticate(request);
      requireReportingPermission(user, 'REPORTING_EXPORT');

      const { report } = request.params;

      try {
        const filters = parseJson(reportingFiltersSchema, request.query ?? {});

        // Fetch all rows (up to 10 000) page by page and stream as CSV.
        const PAGE_SIZE = 500;
        const MAX_ROWS = 10_000;

        const filename = `${report}-${filters.range.from.slice(0, 10)}-${filters.range.to.slice(0, 10)}.csv`;
        reply.header('Content-Type', 'text/csv; charset=utf-8');
        reply.header('Content-Disposition', `attachment; filename="${filename}"`);

        // Write metadata header
        reply.raw.write(
          `# report: ${report}\n` +
          `# from: ${filters.range.from}\n` +
          `# to: ${filters.range.to}\n` +
          `# channel: ${filters.channel}\n` +
          `# exported_at: ${new Date().toISOString()}\n`,
        );

        if (report === 'summary') {
          // Summary: single-row, just write totals
          const data = await reporting.summary(filters);
          reply.raw.write(
            'orders,customers,gross_sales_cents,discounts_cents,tax_cents,units_sold,shipping_cents\n',
          );
          reply.raw.write(
            [
              data.totals.orders,
              data.totals.customers,
              data.totals.grossSalesCents,
              data.totals.discountsCents,
              data.totals.taxCents,
              data.totals.unitsSold,
              data.totals.shippingCents,
            ].join(',') + '\n',
          );
          reply.raw.end();
          return reply;
        }

        if (report === 'sales') {
          const headers = 'period,channel,store_id,terminal_id,orders,customers,gross_sales_cents,discounts_cents,tax_cents,units_sold,shipping_cents\n';
          reply.raw.write(headers);
          let page = 1;
          let totalRows = 0;
          while (totalRows < MAX_ROWS) {
            const data = await reporting.sales({ ...filters, page, pageSize: PAGE_SIZE });
            if (data.items.length === 0) break;
            for (const row of data.items) {
              reply.raw.write(
                [
                  row.period ?? '',
                  row.channel ?? '',
                  row.storeId ?? '',
                  row.terminalId ?? '',
                  row.metrics.orders,
                  row.metrics.customers,
                  row.metrics.grossSalesCents,
                  row.metrics.discountsCents,
                  row.metrics.taxCents,
                  row.metrics.unitsSold,
                  row.metrics.shippingCents,
                ]
                  .map((v) => JSON.stringify(v ?? ''))
                  .join(',') + '\n',
              );
            }
            totalRows += data.items.length;
            if (data.items.length < PAGE_SIZE) break;
            page++;
          }
          reply.raw.end();
          return reply;
        }

        if (report === 'products') {
          const headers = 'product_id,product_name,sku,category,brand,orders,customers,gross_sales_cents,discounts_cents,tax_cents,units_sold,shipping_cents\n';
          reply.raw.write(headers);
          let page = 1;
          let totalRows = 0;
          while (totalRows < MAX_ROWS) {
            const data = await reporting.products({ ...filters, page, pageSize: PAGE_SIZE });
            if (data.items.length === 0) break;
            for (const row of data.items) {
              reply.raw.write(
                [
                  row.productId,
                  row.productName,
                  row.sku ?? '',
                  row.category ?? '',
                  row.brand ?? '',
                  row.metrics.orders,
                  row.metrics.customers,
                  row.metrics.grossSalesCents,
                  row.metrics.discountsCents,
                  row.metrics.taxCents,
                  row.metrics.unitsSold,
                  row.metrics.shippingCents,
                ]
                  .map((v) => JSON.stringify(String(v ?? '')))
                  .join(',') + '\n',
              );
            }
            totalRows += data.items.length;
            if (data.items.length < PAGE_SIZE) break;
            page++;
          }
          reply.raw.end();
          return reply;
        }

        throw new AppError(400, 'INVALID_REPORT', `Unknown report: ${report}`);
      } catch (err) {
        if (err instanceof Error && err.name === 'ZodError') {
          throw new AppError(400, 'INVALID_FILTERS', (err as Error).message);
        }
        throw err;
      }
    },
  );
}
