import { PostHog } from 'posthog-node';
import { z } from 'zod';

// ---------------------------------------------------------
// 1. Unified Event Taxonomy (Zod Enforced, Strictly Typed)
// ---------------------------------------------------------

export enum EventName {
  SignupCompleted = 'account.signup.completed',
  FirstDollarSaved = 'routing.savings.first_dollar',
  UpgradeCompleted = 'billing.upgrade.completed',
}

// Per-event property schemas — no z.any() PII loophole
const SignupProperties = z.object({
  method: z.enum(['github_sso', 'google_sso', 'email']),
}).strict();

const ActivationProperties = z.object({
  savings_amount: z.number().nonnegative(),
}).strict();

const UpgradeProperties = z.object({
  plan: z.enum(['pro', 'business']),
  mrr_increase: z.number().nonnegative(),
}).strict();

const PropertiesMap = {
  [EventName.SignupCompleted]: SignupProperties,
  [EventName.FirstDollarSaved]: ActivationProperties,
  [EventName.UpgradeCompleted]: UpgradeProperties,
} as const;

export const EventSchema = z.object({
  name: z.nativeEnum(EventName),
  tenant_id: z.string().min(1, 'tenant_id is required'),
  product: z.literal('route'),
  properties: z.record(z.unknown()).optional().default({}),
});

export type AnalyticsEvent = z.infer<typeof EventSchema>;

// ---------------------------------------------------------
// 2. NoOp Client for local/test environments
// ---------------------------------------------------------

class NoOpPostHog {
  capture() {}
  identify() {}
  async flushAsync() {}
  async shutdown() {}
}

// ---------------------------------------------------------
// 3. Analytics SDK (PostHog Cloud, Lambda-Safe)
// ---------------------------------------------------------

export class Analytics {
  private client: PostHog | NoOpPostHog;
  public readonly isSessionReplayEnabled = false;

  constructor(client?: PostHog) {
    if (client) {
      this.client = client;
    } else {
      const apiKey = process.env.POSTHOG_API_KEY;
      if (!apiKey) {
        // No key = NoOp. Never silently send to a mock key.
        console.warn('[Analytics] POSTHOG_API_KEY not set — using NoOp client');
        this.client = new NoOpPostHog();
      } else {
        this.client = new PostHog(apiKey, {
          host: 'https://us.i.posthog.com',
          flushAt: 20,       // Batch up to 20 events
          flushInterval: 5000, // Or flush every 5s
        });
      }
    }
  }

  /**
   * Identify a tenant once (on signup). Sets $set properties.
   * Call this instead of embedding $set in every track() call.
   */
  public identify(tenantId: string, properties?: Record<string, unknown>): void {
    this.client.identify({
      distinctId: tenantId,
      properties: { tenant_id: tenantId, ...properties },
    });
  }

  /**
   * Track an event. Uses safeParse — never crashes the caller.
   * Does NOT flush. Call flush() at Lambda teardown.
   */
  public track(event: AnalyticsEvent): boolean {
    // 1. Base schema validation
    const baseResult = EventSchema.safeParse(event);
    if (!baseResult.success) {
      console.error('[Analytics] Invalid event (base):', baseResult.error.format());
      return false;
    }

    // 2. Per-event property validation (strict, no PII loophole)
    const propSchema = PropertiesMap[baseResult.data.name];
    if (propSchema) {
      const propResult = propSchema.safeParse(baseResult.data.properties);
      if (!propResult.success) {
        console.error('[Analytics] Invalid properties:', propResult.error.format());
        return false;
      }
    }

    // 3. Capture — let PostHog assign the timestamp (avoids clock skew)
    this.client.capture({
      distinctId: baseResult.data.tenant_id,
      event: baseResult.data.name,
      properties: {
        product: baseResult.data.product,
        ...baseResult.data.properties,
      },
    });

    return true;
  }

  /**
   * Flush all queued events. Call once at Lambda teardown
   * (e.g., in a Middy middleware or handler's finally block).
   */
  public async flush(): Promise<void> {
    await this.client.flushAsync();
  }

  public async shutdown(): Promise<void> {
    await this.client.shutdown();
  }
}
