import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import pino from 'pino';
import { withTenant } from '../data/db.js';
import { WelfordBaseline, scoreAnomaly, shouldAlert, type CostEvent } from '../detection/scorer.js';
import { config } from '../config/index.js';

const logger = pino({ name: 'api-ingestion' });

const costEventSchema = z.object({
  account_id: z.string(),
  resource_type: z.string(),
  hourly_cost: z.number().min(0),
  region: z.string().default('us-east-1'),
  tags: z.record(z.string()).default({}),
});

const batchSchema = z.object({
  events: z.array(costEventSchema).min(1).max(100),
});

export function registerIngestionRoutes(app: FastifyInstance) {
  // Ingest cost events (from agent or Cost Explorer poller)
  app.post('/api/v1/ingest', async (req, reply) => {
    const tenantId = (req as any).tenantId;
    const { events } = batchSchema.parse(req.body);

    const results = await withTenant(tenantId, async (client) => {
      const anomalies = [];

      for (const event of events) {
        // Fetch or create baseline
        const baselineRow = await client.query(
          `SELECT welford_count, welford_mean, welford_m2, version
           FROM baselines WHERE account_id = $1 AND resource_type = $2`,
          [event.account_id, event.resource_type],
        );

        let baseline: WelfordBaseline;
        let version: number;

        if (baselineRow.rows[0]) {
          baseline = WelfordBaseline.fromJSON({
            count: baselineRow.rows[0].welford_count,
            mean: parseFloat(baselineRow.rows[0].welford_mean),
            m2: parseFloat(baselineRow.rows[0].welford_m2),
          });
          version = baselineRow.rows[0].version;
        } else {
          baseline = new WelfordBaseline();
          version = 0;
        }

        // Score before updating baseline
        const score = scoreAnomaly({
          cost: event.hourly_cost,
          mean: baseline.mean,
          stddev: baseline.stddev,
        });

        const isAnomaly = shouldAlert(score, config.ANOMALY_THRESHOLD);

        // Update baseline with optimistic locking (BMad must-have: concurrent Welford corruption)
        baseline.update(event.hourly_cost);
        const bj = baseline.toJSON();

        if (version === 0) {
          await client.query(
            `INSERT INTO baselines (tenant_id, account_id, resource_type, welford_count, welford_mean, welford_m2, version)
             VALUES ($1, $2, $3, $4, $5, $6, 1)
             ON CONFLICT (tenant_id, account_id, resource_type) DO NOTHING`,
            [tenantId, event.account_id, event.resource_type, bj.count, bj.mean, bj.m2],
          );
        } else {
          const updated = await client.query(
            `UPDATE baselines SET welford_count = $1, welford_mean = $2, welford_m2 = $3,
                    version = version + 1, updated_at = now()
             WHERE account_id = $4 AND resource_type = $5 AND version = $6`,
            [bj.count, bj.mean, bj.m2, event.account_id, event.resource_type, version],
          );
          if (updated.rowCount === 0) {
            logger.warn({ accountId: event.account_id, resourceType: event.resource_type },
              'Optimistic lock conflict — baseline update skipped');
          }
        }

        // Record anomaly if threshold exceeded
        if (isAnomaly) {
          await client.query(
            `INSERT INTO anomalies (tenant_id, account_id, resource_type, region, hourly_cost, score, baseline_mean, baseline_stddev, tags)
             VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
            [tenantId, event.account_id, event.resource_type, event.region, event.hourly_cost, score, baseline.mean, baseline.stddev, JSON.stringify(event.tags)],
          );
          anomalies.push({ ...event, score, isAnomaly: true });
        }
      }

      return anomalies;
    });

    return { ingested: events.length, anomalies: results.length, details: results };
  });
}
