/**
 * Telemetry types — event schema, collection policies, and cluster types.
 * See docs/WINDSURF/CLIENT_TELEMETRY_DESIGN.md for full design.
 */

import { z } from 'zod';

// ─── Enums ──────────────────────────────────────────────────────────

export const PlatformEnum = z.enum(['ios', 'android', 'web', 'desktop']);
export const ChannelEnum = z.enum([
  'mobile_app',
  'keyboard_extension',
  'web_app',
  'desktop_app',
  'backend_service',
  'native',
]);
export const OsFamilyEnum = z.enum([
  'ios',
  'android',
  'macos',
  'windows',
  'linux',
  'chromeos',
  'other',
]);
export const EventTypeEnum = z.enum(['debug', 'info', 'warn', 'error', 'fatal']);
export const ReleaseChannelEnum = z.enum(['dev', 'beta', 'prod']);

// ─── Telemetry Event ────────────────────────────────────────────────

export const TelemetryEventSchema = z
  .object({
    // Identity
    id: z.string().uuid(),
    productId: z.string().min(1),
    userId: z.string().optional(),
    anonymousInstallId: z.string().uuid().optional(),
    sessionId: z.string().min(1),
    requestId: z.string().optional(),

    // Source classification
    platform: PlatformEnum,
    channel: ChannelEnum,
    osFamily: OsFamilyEnum,
    osVersion: z.string().optional(),
    deviceModel: z.string().optional(),
    locale: z.string().optional(),
    timezone: z.string().optional(),

    // App release
    appVersion: z.string().min(1),
    buildNumber: z.string().min(1),
    releaseChannel: ReleaseChannelEnum,

    // Event semantics
    eventType: EventTypeEnum,
    module: z.string().min(1),
    feature: z.string().optional(),
    eventName: z.string().min(1),

    // Error & diagnostics
    errorDomain: z.string().optional(),
    errorCode: z.string().optional(),
    message: z.string().max(512).optional(),
    stackTrace: z.string().max(8192).optional(),
    fingerprint: z.string().optional(),

    // Structured metadata
    tags: z.record(z.string().max(128)).optional(),
    metrics: z.record(z.number()).optional(),
    context: z.record(z.unknown()).optional(),

    // Timing (client provides occurredAt)
    occurredAt: z.string().datetime(),
  })
  .refine(e => e.userId || e.anonymousInstallId, {
    message: 'At least one of userId or anonymousInstallId is required',
  });

export type TelemetryEvent = z.infer<typeof TelemetryEventSchema>;

/** Server-enriched event document stored in Cosmos. */
export interface TelemetryEventDoc extends TelemetryEvent {
  pk: string; // ${productId}:${yyyyMM}:${platform}
  receivedAt: string;
  ttl: number; // Cosmos TTL in seconds
  countryCode?: string;
  regionCode?: string;
}

// ─── Batch Ingest Request ───────────────────────────────────────────

export const TelemetryIngestRequestSchema = z.object({
  productId: z.string().min(1),
  events: z.array(TelemetryEventSchema).min(1).max(50),
  clientClockSkewMs: z.number().optional(),
});

export type TelemetryIngestRequest = z.infer<typeof TelemetryIngestRequestSchema>;

export interface TelemetryIngestResponse {
  accepted: number;
  rejected: number;
  errors?: Array<{ index: number; reason: string }>;
  serverTime: string;
}

// ─── Collection Policy ──────────────────────────────────────────────

export const TelemetryTargetingSchema = z.object({
  userIds: z.array(z.string()).optional(),
  anonymousInstallIds: z.array(z.string()).optional(),
  platforms: z.array(PlatformEnum).optional(),
  channels: z.array(ChannelEnum).optional(),
  osFamilies: z.array(OsFamilyEnum).optional(),
  appVersions: z.array(z.string()).optional(),
  appVersionRange: z
    .object({
      min: z.string().optional(),
      max: z.string().optional(),
    })
    .optional(),
  buildNumbers: z.array(z.string()).optional(),
  buildNumberRange: z
    .object({
      min: z.number().optional(),
      max: z.number().optional(),
    })
    .optional(),
  countryCodes: z.array(z.string()).optional(),
  regionCodes: z.array(z.string()).optional(),
  releaseChannels: z.array(ReleaseChannelEnum).optional(),
  percentage: z.number().min(0).max(100).optional(),
});

export const CreatePolicySchema = z.object({
  name: z.string().min(1).max(200),
  description: z.string().default(''),
  enabled: z.boolean().default(true),
  priority: z.number().int().min(0).max(999).default(50),
  eventTypes: z.array(EventTypeEnum).default(['warn', 'error', 'fatal']),
  modules: z.array(z.string()).default([]),
  samplingRate: z.number().min(0).max(1).default(1.0),
  targeting: TelemetryTargetingSchema.default({}),
  startsAt: z.string().datetime().optional(),
  expiresAt: z.string().datetime().optional(),
});

export const UpdatePolicySchema = z.object({
  name: z.string().min(1).max(200).optional(),
  description: z.string().optional(),
  enabled: z.boolean().optional(),
  priority: z.number().int().min(0).max(999).optional(),
  eventTypes: z.array(EventTypeEnum).optional(),
  modules: z.array(z.string()).optional(),
  samplingRate: z.number().min(0).max(1).optional(),
  targeting: TelemetryTargetingSchema.optional(),
  startsAt: z.string().datetime().optional().nullable(),
  expiresAt: z.string().datetime().optional().nullable(),
});

export type CreatePolicyInput = z.infer<typeof CreatePolicySchema>;
export type UpdatePolicyInput = z.infer<typeof UpdatePolicySchema>;

export interface TelemetryCollectionPolicyDoc {
  id: string;
  productId: string;
  name: string;
  description: string;
  enabled: boolean;
  priority: number;
  eventTypes: string[];
  modules: string[];
  samplingRate: number;
  targeting: z.infer<typeof TelemetryTargetingSchema>;
  startsAt?: string;
  expiresAt?: string;
  createdAt: string;
  updatedAt: string;
  createdBy: string;
}

// ─── Collection Config Response (for clients) ──────────────────────

export interface TelemetryCollectionConfig {
  enabled: boolean;
  eventTypes: string[];
  modules: string[];
  samplingRates: {
    debug: number;
    info: number;
    warn: number;
    error: number;
    fatal: number;
  };
  batchSize: number;
  flushIntervalMs: number;
  maxQueueSize: number;
}

// ─── Error Cluster ──────────────────────────────────────────────────

export const ClusterStatusEnum = z.enum(['open', 'resolved', 'ignored']);

export interface TelemetryErrorCluster {
  id: string; // ${fingerprint}:${yyyyMM}
  pk: string; // ${productId}:${platform}:${module}
  productId: string;
  fingerprint: string;

  platform: string;
  channel: string;
  module: string;
  eventName: string;

  affectedVersions: Array<{
    appVersion: string;
    buildNumber: string;
    count: number;
    lastSeenAt: string;
  }>;

  firstSeenAt: string;
  lastSeenAt: string;
  totalCount: number;
  affectedUserIds: string[];
  affectedInstallIds: string[];
  affectedOsFamilies: string[];

  sampleErrorDomain?: string;
  sampleErrorCode?: string;
  sampleMessage?: string;
  severity: 'warn' | 'error' | 'fatal';
  status: 'open' | 'resolved' | 'ignored';
  resolvedBy?: string;
  resolvedAt?: string;
  ttl: number;
}

export const UpdateClusterSchema = z.object({
  status: ClusterStatusEnum,
});

// ─── Query / Admin types ────────────────────────────────────────────

export const TelemetryQuerySchema = z.object({
  userId: z.string().optional(),
  anonymousInstallId: z.string().optional(),
  platform: z.string().optional(),
  channel: z.string().optional(),
  osFamily: z.string().optional(),
  appVersion: z.string().optional(),
  buildNumber: z.string().optional(),
  module: z.string().optional(),
  eventName: z.string().optional(),
  eventType: z.string().optional(),
  from: z.string().datetime().optional(),
  to: z.string().datetime().optional(),
  limit: z.coerce.number().int().min(1).max(200).default(50),
  continuationToken: z.string().optional(),
});

export type TelemetryQueryInput = z.infer<typeof TelemetryQuerySchema>;

// ─── Ingestion Metrics (in-memory counters) ─────────────────────────

export interface TelemetryMetrics {
  totalEventsIngested: number;
  totalEventsRejected: number;
  totalBatchRequests: number;
  totalRateLimited: number;
  totalPiiBlocked: number;
  totalDuplicatesDropped: number;
  uptimeSince: string;
}
