/**
 * Automated Triggers for Remote Diagnostics
 * Error-threshold triggers and crash-triggered auto sessions.
 */

import { z } from 'zod';
import crypto from 'node:crypto';
import type { DebugSessionDoc } from './types.js';
import { createSession } from './repository.js';
import { getRegisteredContainer } from '@bytelyst/cosmos';

function logTriggerError(message: string, error: unknown): void {
  process.stderr.write(
    JSON.stringify({
      level: 'error',
      module: 'diagnostics.auto-triggers',
      message,
      error: error instanceof Error ? error.message : String(error),
    }) + '\n'
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Trigger Configuration Types
// ─────────────────────────────────────────────────────────────────────────────

export const TriggerConfigSchema = z.object({
  id: z.string(),
  productId: z.string(),
  name: z.string(),
  enabled: z.boolean().default(true),

  // Trigger conditions
  condition: z.discriminatedUnion('type', [
    z.object({
      type: z.literal('error_rate'),
      threshold: z.number().min(0).max(1), // 0.0 - 1.0 (0% to 100%)
      windowMinutes: z.number().min(1).max(60),
      minEvents: z.number().min(10), // Minimum events before triggering
    }),
    z.object({
      type: z.literal('crash_count'),
      threshold: z.number().min(1),
      windowMinutes: z.number().min(1).max(60),
    }),
    z.object({
      type: z.literal('fatal_log'),
      count: z.number().min(1),
      windowMinutes: z.number().min(1).max(60),
    }),
  ]),

  // Session to create when triggered
  sessionConfig: z.object({
    collectionLevel: z.enum(['standard', 'debug', 'trace']).default('debug'),
    captureLogs: z.boolean().default(true),
    captureNetwork: z.boolean().default(true),
    captureScreenshots: z.boolean().default(true),
    screenshotOnError: z.boolean().default(true),
    maxDurationMinutes: z.number().min(5).max(1440).default(60),
  }),

  // Notifications
  notifications: z.object({
    slackWebhook: z.string().optional(),
    teamsWebhook: z.string().optional(),
    emailAdmins: z.boolean().default(true),
    pagerDutyKey: z.string().optional(),
  }),

  // Cooldown to prevent spam
  cooldownMinutes: z.number().min(5).max(1440).default(60),
  lastTriggeredAt: z.string().datetime().optional(),

  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
});

export type TriggerConfig = z.infer<typeof TriggerConfigSchema>;

export const CreateTriggerConfigSchema = TriggerConfigSchema.omit({
  id: true,
  lastTriggeredAt: true,
  createdAt: true,
  updatedAt: true,
});

export type CreateTriggerConfigInput = z.infer<typeof CreateTriggerConfigSchema>;

// ─────────────────────────────────────────────────────────────────────────────
// Repository Functions
// ─────────────────────────────────────────────────────────────────────────────

const TRIGGER_CONTAINER = 'diagnostic_triggers';

export async function getTriggerContainer() {
  return getRegisteredContainer(TRIGGER_CONTAINER);
}

export async function createTriggerConfig(input: CreateTriggerConfigInput): Promise<TriggerConfig> {
  const container = await getTriggerContainer();
  const now = new Date().toISOString();

  const config: TriggerConfig = {
    id: `trig_${crypto.randomUUID().replace(/-/g, '')}`,
    ...input,
    lastTriggeredAt: undefined,
    createdAt: now,
    updatedAt: now,
  };

  await container.items.create(config);
  return config;
}

export async function getTriggerConfig(id: string): Promise<TriggerConfig | null> {
  const container = await getTriggerContainer();
  try {
    const { resource } = await container.item(id, id).read<TriggerConfig>();
    return resource || null;
  } catch {
    return null;
  }
}

export async function listTriggerConfigs(productId: string): Promise<TriggerConfig[]> {
  const container = await getTriggerContainer();
  const query = {
    query: 'SELECT * FROM c WHERE c.productId = @productId ORDER BY c.createdAt DESC',
    parameters: [{ name: '@productId', value: productId }],
  };

  const { resources } = await container.items.query<TriggerConfig>(query).fetchAll();
  return resources;
}

export async function updateTriggerConfig(
  id: string,
  updates: Partial<Omit<TriggerConfig, 'id' | 'productId' | 'createdAt'>>
): Promise<TriggerConfig | null> {
  const container = await getTriggerContainer();

  const existing = await getTriggerConfig(id);
  if (!existing) return null;

  const updated: TriggerConfig = {
    ...existing,
    ...updates,
    updatedAt: new Date().toISOString(),
  };

  await container.item(id, id).replace(updated);
  return updated;
}

export async function deleteTriggerConfig(id: string): Promise<boolean> {
  const container = await getTriggerContainer();
  try {
    await container.item(id, id).delete();
    return true;
  } catch {
    return false;
  }
}

export async function recordTriggerExecution(id: string, sessionId: string): Promise<void> {
  const container = await getTriggerContainer();
  const now = new Date().toISOString();

  await container.item(id, id).patch({
    operations: [
      { op: 'set', path: '/lastTriggeredAt', value: now },
      { op: 'incr', path: '/triggerCount', value: 1 },
      { op: 'set', path: '/lastSessionId', value: sessionId },
    ],
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// Trigger Evaluation Logic
// ─────────────────────────────────────────────────────────────────────────────

interface ErrorStats {
  totalEvents: number;
  errorCount: number;
  fatalCount: number;
  crashCount: number;
  errorRate: number;
}

/**
 * Query error statistics from telemetry events.
 */
export async function queryErrorStats(
  productId: string,
  windowMinutes: number
): Promise<ErrorStats> {
  const telemetryContainer = await getRegisteredContainer('telemetry_events');
  const since = new Date(Date.now() - windowMinutes * 60 * 1000).toISOString();

  // Query for error events
  const errorQuery = {
    query: `
      SELECT VALUE COUNT(1)
      FROM c
      WHERE c.productId = @productId
        AND c.timestamp >= @since
        AND c.eventType = 'error'
    `,
    parameters: [
      { name: '@productId', value: productId },
      { name: '@since', value: since },
    ],
  };

  // Query for total events
  const totalQuery = {
    query: `
      SELECT VALUE COUNT(1)
      FROM c
      WHERE c.productId = @productId
        AND c.timestamp >= @since
    `,
    parameters: [
      { name: '@productId', value: productId },
      { name: '@since', value: since },
    ],
  };

  // Query for fatal logs from diagnostics
  const fatalQuery = {
    query: `
      SELECT VALUE COUNT(1)
      FROM c
      WHERE c.productId = @productId
        AND c.timestamp >= @since
        AND c.level = 'fatal'
    `,
    parameters: [
      { name: '@productId', value: productId },
      { name: '@since', value: since },
    ],
  };

  const [{ resources: errorResult }, { resources: totalResult }, { resources: fatalResult }] =
    await Promise.all([
      telemetryContainer.items.query(errorQuery).fetchNext(),
      telemetryContainer.items.query(totalQuery).fetchNext(),
      telemetryContainer.items.query(fatalQuery).fetchNext(),
    ]);

  const errorCount = errorResult[0] || 0;
  const totalEvents = totalResult[0] || 0;
  const fatalCount = fatalResult[0] || 0;

  return {
    totalEvents,
    errorCount,
    fatalCount,
    crashCount: fatalCount, // Treat fatal as crashes for now
    errorRate: totalEvents > 0 ? errorCount / totalEvents : 0,
  };
}

/**
 * Evaluate a single trigger configuration.
 */
export async function evaluateTrigger(
  trigger: TriggerConfig,
  adminUserId: string
): Promise<{ triggered: boolean; reason?: string; session?: DebugSessionDoc }> {
  // Check cooldown
  if (trigger.lastTriggeredAt) {
    const lastTrigger = new Date(trigger.lastTriggeredAt).getTime();
    const cooldownMs = trigger.cooldownMinutes * 60 * 1000;
    if (Date.now() - lastTrigger < cooldownMs) {
      return { triggered: false, reason: 'Cooldown active' };
    }
  }

  // Check if trigger is enabled
  if (!trigger.enabled) {
    return { triggered: false, reason: 'Trigger disabled' };
  }

  const stats = await queryErrorStats(trigger.productId, trigger.condition.windowMinutes);

  switch (trigger.condition.type) {
    case 'error_rate': {
      if (stats.totalEvents < trigger.condition.minEvents) {
        return { triggered: false, reason: 'Insufficient events' };
      }
      if (stats.errorRate >= trigger.condition.threshold) {
        // Trigger auto-session
        const session = await createAutoSession(trigger, adminUserId);
        await recordTriggerExecution(trigger.id, session.id);
        await sendTriggerNotifications(trigger, session, stats);
        return {
          triggered: true,
          reason: `Error rate ${(stats.errorRate * 100).toFixed(1)}% exceeded threshold ${(trigger.condition.threshold * 100).toFixed(1)}%`,
          session,
        };
      }
      break;
    }

    case 'crash_count': {
      if (stats.crashCount >= trigger.condition.threshold) {
        const session = await createAutoSession(trigger, adminUserId);
        await recordTriggerExecution(trigger.id, session.id);
        await sendTriggerNotifications(trigger, session, stats);
        return {
          triggered: true,
          reason: `${stats.crashCount} crashes in ${trigger.condition.windowMinutes} minutes`,
          session,
        };
      }
      break;
    }

    case 'fatal_log': {
      if (stats.fatalCount >= trigger.condition.count) {
        const session = await createAutoSession(trigger, adminUserId);
        await recordTriggerExecution(trigger.id, session.id);
        await sendTriggerNotifications(trigger, session, stats);
        return {
          triggered: true,
          reason: `${stats.fatalCount} fatal logs in ${trigger.condition.windowMinutes} minutes`,
          session,
        };
      }
      break;
    }
  }

  return { triggered: false, reason: 'Threshold not met' };
}

/**
 * Create an auto-triggered debug session.
 */
async function createAutoSession(
  trigger: TriggerConfig,
  adminUserId: string
): Promise<DebugSessionDoc> {
  const now = new Date().toISOString();
  const expiresAt = new Date(
    Date.now() + trigger.sessionConfig.maxDurationMinutes * 60 * 1000
  ).toISOString();
  const id = `ds_${crypto.randomUUID().replace(/-/g, '')}`;

  const session: DebugSessionDoc = {
    id,
    productId: trigger.productId,
    status: 'active',
    collectionLevel: trigger.sessionConfig.collectionLevel,
    captureLogs: trigger.sessionConfig.captureLogs,
    captureNetwork: trigger.sessionConfig.captureNetwork,
    captureScreenshots: trigger.sessionConfig.captureScreenshots,
    screenshotOnError: trigger.sessionConfig.screenshotOnError,
    maxDurationMinutes: trigger.sessionConfig.maxDurationMinutes,
    createdAt: now,
    updatedAt: now,
    startedAt: now,
    expiresAt,
    logCount: 0,
    traceCount: 0,
    screenshotCount: 0,
    createdBy: adminUserId,
  };

  await createSession(session);
  return session;
}

/**
 * Send notifications when trigger fires.
 */
async function sendTriggerNotifications(
  trigger: TriggerConfig,
  session: DebugSessionDoc,
  stats: ErrorStats
): Promise<void> {
  const { notifications } = trigger;

  // Slack webhook
  if (notifications.slackWebhook) {
    try {
      await fetch(notifications.slackWebhook, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          text: `🚨 Auto-trigger fired: ${trigger.name}`,
          attachments: [
            {
              color: 'danger',
              fields: [
                { title: 'Product', value: trigger.productId, short: true },
                { title: 'Session', value: session.id, short: true },
                {
                  title: 'Error Rate',
                  value: `${(stats.errorRate * 100).toFixed(1)}%`,
                  short: true,
                },
                { title: 'Crashes', value: stats.crashCount.toString(), short: true },
              ],
            },
          ],
        }),
      });
    } catch (err) {
      // Log but don't fail
      logTriggerError('Failed to send Slack notification', err);
    }
  }

  // Teams webhook
  if (notifications.teamsWebhook) {
    try {
      await fetch(notifications.teamsWebhook, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          '@type': 'MessageCard',
          '@context': 'https://schema.org/extensions',
          themeColor: 'FF0000',
          title: `🚨 Auto-trigger fired: ${trigger.name}`,
          text: `Error rate ${(stats.errorRate * 100).toFixed(1)}% exceeded threshold`,
          sections: [
            {
              facts: [
                { name: 'Product:', value: trigger.productId },
                { name: 'Session:', value: session.id },
                { name: 'Crashes:', value: stats.crashCount.toString() },
              ],
            },
          ],
        }),
      });
    } catch (err) {
      logTriggerError('Failed to send Teams notification', err);
    }
  }

  // PagerDuty (future)
  if (notifications.pagerDutyKey) {
    // Integration with PagerDuty Events API v2
    // https://developer.pagerduty.com/docs/events-api-v2-overview/
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// Background Job Runner
// ─────────────────────────────────────────────────────────────────────────────

/**
 * Run all triggers for a product.
 * Called by the scheduled job every 5 minutes.
 */
export async function runAllTriggers(
  productId: string,
  adminUserId: string
): Promise<Array<{ triggerId: string; triggered: boolean; reason?: string }>> {
  const triggers = await listTriggerConfigs(productId);
  const results: Array<{ triggerId: string; triggered: boolean; reason?: string }> = [];

  for (const trigger of triggers) {
    const result = await evaluateTrigger(trigger, adminUserId);
    results.push({
      triggerId: trigger.id,
      triggered: result.triggered,
      reason: result.reason,
    });
  }

  return results;
}
