/// <reference types="bun-types" />
import type { Database } from 'bun:sqlite';
import { GroupSyncService } from './services/group-sync';
import { ContactsService } from './services/contacts';
import { Migrator } from './db/migrator';
import { Metrics } from './services/metrics';
import { AllowedGroups } from './services/allowed-groups';
import { db } from './db';
import { handleMetricsRequest } from './http/metrics';
import { handleHealthRequest } from './http/health';
import { REQUIRED_ENV } from './env/required';
import { startServices } from './http/bootstrap';
import { handleMessageUpsert as handleMessageUpsertFn } from './http/webhook-handler';

type WebhookPayload = {
  event: string;
  instance: string;
  data: any;
  // Other fields from Evolution API
};

export class WebhookServer {
  static dbInstance: Database = db;

  private static getBaseUrl(request: Request): string {
    const proto = request.headers.get('x-forwarded-proto') || 'http';
    const host =
      request.headers.get('x-forwarded-host') || request.headers.get('host');
    return `${proto}://${host}`;
  }

  private static shouldVerifyInstance(): boolean {
    return process.env.NODE_ENV !== 'test' || !!process.env.TEST_VERIFY_INSTANCE;
  }

  private static async handleGroupsUpsert(): Promise<void> {
    await GroupSyncService.syncGroups();
    GroupSyncService.refreshActiveGroupsCache();
    const changed = GroupSyncService.getLastChangedActive();
    if (changed.length > 0) {
      await GroupSyncService.syncMembersForGroups(changed);
    } else {
      await GroupSyncService.syncMembersForActiveGroups();
    }
  }

  /** Route a parsed webhook payload to the appropriate handler. */
  private static async routeWebhookEvent(data: any, evt: string): Promise<void> {
    const evtNorm = evt.toLowerCase().replace(/_/g, '.');

    try {
      Metrics.inc(`webhook_events_total_${evtNorm.replace(/\./g, '_')}`);
    } catch {}

    switch (evtNorm) {
      case 'messages.upsert':
        if (process.env.NODE_ENV !== 'test') {
          console.log('ℹ️ Handling message upsert:', {
            groupId: data?.key?.remoteJid,
            message: data?.message?.conversation,
            rawEvent: evt
          });
        }
        await WebhookServer.handleMessageUpsert(data);
        break;
      case 'contacts.update':
      case 'chats.update':
        if (process.env.NODE_ENV !== 'test') {
          console.log('ℹ️ Handling contacts/chats update event:', { rawEvent: evt });
        }
        ContactsService.updateFromWebhook(data);
        break;
      case 'groups.upsert':
        if (process.env.NODE_ENV !== 'test') {
          console.log('ℹ️ Handling groups upsert event:', { rawEvent: evt });
        }
        try {
          await WebhookServer.handleGroupsUpsert();
        } catch (e) {
          console.error('❌ Error handling groups.upsert:', e);
        }
        break;
    }
  }

  static async handleRequest(request: Request): Promise<Response> {
    const url = new URL(request.url);
    if (url.pathname.endsWith('/metrics')) {
      return await handleMetricsRequest(request, WebhookServer.dbInstance);
    }
    if (url.pathname.endsWith('/health')) {
      return await handleHealthRequest(url, WebhookServer.dbInstance);
    }

    if (process.env.NODE_ENV !== 'test') {
      console.log('ℹ️ Incoming webhook request:');
    }

    if (request.method !== 'POST') {
      return new Response('🚫 Method not allowed', { status: 405 });
    }

    const contentType = request.headers.get('content-type');
    if (!contentType?.includes('application/json')) {
      return new Response('🚫 Invalid content type', { status: 400 });
    }

    try {
      const payload = (await request.json()) as WebhookPayload;

      if (!payload.event || !payload.instance) {
        return new Response('🚫 Invalid payload', { status: 400 });
      }

      if (WebhookServer.shouldVerifyInstance() && payload.instance !== process.env.EVOLUTION_API_INSTANCE) {
        return new Response('🚫 Invalid instance', { status: 403 });
      }

      await WebhookServer.routeWebhookEvent(payload.data, String(payload.event));

      return new Response('OK', { status: 200 });
    } catch (error) {
      console.error('❌ Error processing webhook:', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
        time: new Date().toISOString()
      });
      try {
        Metrics.inc('webhook_errors_total');
      } catch {}
      return new Response('Invalid request', { status: 400 });
    }
  }

  static async handleMessageUpsert(data: any) {
    return await handleMessageUpsertFn(data, WebhookServer.dbInstance);
  }

  static validateEnv() {
    console.log('ℹ️ Checking environment variables...');
    console.log(
      'EVOLUTION_API_URL:',
      process.env.EVOLUTION_API_URL ? '***' : 'MISSING'
    );
    console.log(
      'EVOLUTION_API_INSTANCE:',
      process.env.EVOLUTION_API_INSTANCE || 'MISSING'
    );
    console.log(
      'WEBHOOK_URL:',
      process.env.WEBHOOK_URL
        ? `${process.env.WEBHOOK_URL.substring(0, 20)}...`
        : 'NOT SET'
    );
    console.log(
      'WHATSAPP_COMMUNITY_ID:',
      process.env.WHATSAPP_COMMUNITY_ID
        ? '***'
        : 'NOT SET (se mostrarán comunidades disponibles)'
    );

    const missing = REQUIRED_ENV.filter(v => !process.env[v]);
    if (missing.length) {
      console.error('❌ Missing required environment variables:');
      missing.forEach(v => console.error(`- ${v}`));
      console.error('Add these to your CapRover environment configuration');
      process.exit(1);
    }

    if (
      process.env.CHATBOT_PHONE_NUMBER &&
      !/^\d+$/.test(process.env.CHATBOT_PHONE_NUMBER)
    ) {
      console.error('❌ CHATBOT_PHONE_NUMBER must contain only digits');
      process.exit(1);
    }
  }

  static async start() {
    this.validateEnv();

    // Run database migrations (up-only) before starting services
    await Migrator.migrateToLatest(this.dbInstance);

    // Etapa 7: seed inicial de grupos permitidos desde ALLOWED_GROUPS (best-effort)
    try {
      AllowedGroups.seedFromEnv();
    } catch {}

    const PORT = process.env.PORT || '3007';
    console.log('✅ Environment variables validated');
    // A0: pre-crear contadores para que aparezcan en /metrics
    try {
      Metrics.inc('onboarding_prompts_sent_total', 0);
      Metrics.inc('onboarding_prompts_skipped_total', 0);
      Metrics.inc('onboarding_assign_failures_total', 0);

      // Precalentar métricas de reacciones por emoji
      for (const emoji of ['robot', 'warn', 'check', 'other']) {
        Metrics.inc('reactions_enqueued_total', 0, { emoji });
        Metrics.inc('reactions_sent_total', 0, { emoji });
        Metrics.inc('reactions_failed_total', 0, { emoji });
      }
    } catch {}

    if (process.env.NODE_ENV !== 'test') {
      try {
        await startServices(this.dbInstance);
      } catch (error) {
        console.error(
          '❌ Failed to setup webhook:',
          error instanceof Error ? error.message : error
        );
        process.exit(1);
      }
    }

    const server = Bun.serve({
      port: parseInt(PORT),
      fetch: (request: Request) => WebhookServer.handleRequest(request)
    });
    console.log(`Server running on port ${PORT}`);
    return server;
  }
}
