/**
 * WhatsApp Router Service
 *
 * Multi-tenant message router that maps incoming phone_number_id to an organization.
 * Uses Redis caching with 1-hour TTL for fast lookups, with graceful degradation
 * when Redis is unavailable.
 */

import { redis, redisConfigured } from '@/lib/redis';
import { orgWhatsAppKeysService } from '@/lib/services/org-whatsapp-keys.service';
import { twilioWhatsAppService } from '@/lib/services/twilio-whatsapp.service';
import { organizationsAdapter } from '@/lib/adapters/organizations';
import { logger } from '@/lib/logger';

export interface RoutedOrganization {
  id: string;
  name: string;
  ownerId: string;
  industry: string | null;
  sector: string | null;
}

class WhatsAppRouterService {
  private readonly CACHE_PREFIX = 'wa-route:';
  private readonly TWILIO_CACHE_PREFIX = 'wa-route-twilio:';
  private readonly CACHE_TTL_SECONDS = 3600; // 1 hour max per security plan

  private cacheKey(phoneNumberId: string): string {
    return `${this.CACHE_PREFIX}${phoneNumberId}`;
  }

  private twilioCacheKey(twilioNumber: string): string {
    return `${this.TWILIO_CACHE_PREFIX}${twilioNumber}`;
  }

  /**
   * Resolve phone_number_id to organization.
   * 1. Check Redis cache
   * 2. If miss, query DB via orgWhatsAppKeysService
   * 3. If found, fetch full org data (industry, sector) from organizationsAdapter
   * 4. Cache the result
   * 5. Return null if no org found
   */
  async resolveOrganization(phoneNumberId: string): Promise<RoutedOrganization | null> {
    // 1. Check Redis cache
    if (redisConfigured) {
      try {
        const cached = await redis.get<RoutedOrganization>(this.cacheKey(phoneNumberId));
        if (cached) {
          logger.debug('WhatsApp route cache hit', { phoneNumberId });
          return cached;
        }
        logger.debug('WhatsApp route cache miss', { phoneNumberId });
      } catch (error) {
        logger.error('WhatsApp route cache read failed', error, { phoneNumberId });
      }
    }

    // 2. Query DB for org by phone number ID
    const baseOrg = await orgWhatsAppKeysService.getOrgByPhoneNumberId(phoneNumberId);
    if (!baseOrg) {
      logger.debug('No organization found for phone_number_id', { phoneNumberId });
      return null;
    }

    // 3. Fetch full org data for industry/sector
    let industry: string | null = null;
    let sector: string | null = null;

    try {
      const fullOrg = await organizationsAdapter.findUnique({
        where: { id: baseOrg.id },
        select: { industry: true, sector: true },
      }) as Record<string, unknown> | null;

      if (fullOrg) {
        industry = (fullOrg.industry as string) ?? null;
        sector = (fullOrg.sector as string) ?? null;
      }
    } catch (error) {
      logger.error('Failed to fetch org industry/sector', error, { organizationId: baseOrg.id });
    }

    const routed: RoutedOrganization = {
      id: baseOrg.id,
      name: baseOrg.name,
      ownerId: baseOrg.ownerId,
      industry,
      sector,
    };

    // 4. Cache the result
    if (redisConfigured) {
      try {
        await redis.set(this.cacheKey(phoneNumberId), routed, { ex: this.CACHE_TTL_SECONDS });
      } catch (error) {
        logger.error('WhatsApp route cache write failed', error, { phoneNumberId });
      }
    }

    logger.info('WhatsApp route resolved', {
      phoneNumberId,
      organizationId: routed.id,
      organizationName: routed.name,
    });

    return routed;
  }

  /**
   * Resolve a Twilio WhatsApp number (e.g. "whatsapp:+14155238886") to an organization.
   * Mirrors resolveOrganization but for Twilio Sandbox / Twilio WABA deployments.
   */
  async resolveOrganizationByTwilioNumber(
    twilioNumber: string,
  ): Promise<RoutedOrganization | null> {
    // 1. Check Redis cache
    if (redisConfigured) {
      try {
        const cached = await redis.get<RoutedOrganization>(
          this.twilioCacheKey(twilioNumber),
        );
        if (cached) {
          logger.debug('Twilio route cache hit', { twilioNumber });
          return cached;
        }
        logger.debug('Twilio route cache miss', { twilioNumber });
      } catch (error) {
        logger.error('Twilio route cache read failed', error, { twilioNumber });
      }
    }

    // 2. Query DB for org by Twilio number
    const baseOrg = await twilioWhatsAppService.getOrgByTwilioNumber(twilioNumber);
    if (!baseOrg) {
      logger.debug('No organization found for Twilio number', { twilioNumber });
      return null;
    }

    // 3. Fetch full org data for industry/sector
    let industry: string | null = null;
    let sector: string | null = null;

    try {
      const fullOrg = (await organizationsAdapter.findUnique({
        where: { id: baseOrg.id },
        select: { industry: true, sector: true },
      })) as Record<string, unknown> | null;

      if (fullOrg) {
        industry = (fullOrg.industry as string) ?? null;
        sector = (fullOrg.sector as string) ?? null;
      }
    } catch (error) {
      logger.error('Failed to fetch org industry/sector (Twilio)', error, {
        organizationId: baseOrg.id,
      });
    }

    const routed: RoutedOrganization = {
      id: baseOrg.id,
      name: baseOrg.name,
      ownerId: baseOrg.ownerId,
      industry,
      sector,
    };

    // 4. Cache the result
    if (redisConfigured) {
      try {
        await redis.set(this.twilioCacheKey(twilioNumber), routed, {
          ex: this.CACHE_TTL_SECONDS,
        });
      } catch (error) {
        logger.error('Twilio route cache write failed', error, { twilioNumber });
      }
    }

    logger.info('Twilio route resolved', {
      twilioNumber,
      organizationId: routed.id,
      organizationName: routed.name,
    });

    return routed;
  }

  /**
   * Invalidate cache for a phone_number_id.
   * Called when an org disconnects WhatsApp.
   */
  async invalidateCache(phoneNumberId: string): Promise<void> {
    if (!redisConfigured) return;

    try {
      await redis.del(this.cacheKey(phoneNumberId));
      logger.debug('WhatsApp route cache invalidated', { phoneNumberId });
    } catch (error) {
      logger.error('WhatsApp route cache invalidation failed', error, { phoneNumberId });
    }
  }

  /**
   * Invalidate all cached entries for an organization.
   * Called when org settings change.
   */
  async invalidateOrgCache(organizationId: string): Promise<void> {
    if (!redisConfigured) return;

    try {
      const org = await organizationsAdapter.findUnique({
        where: { id: organizationId },
        select: { whatsappPhoneNumberId: true },
      }) as Record<string, unknown> | null;

      if (org?.whatsappPhoneNumberId) {
        await redis.del(this.cacheKey(org.whatsappPhoneNumberId as string));
        logger.debug('WhatsApp route cache invalidated for org', {
          organizationId,
          phoneNumberId: org.whatsappPhoneNumberId,
        });
      }
    } catch (error) {
      logger.error('WhatsApp route org cache invalidation failed', error, { organizationId });
    }
  }

  /**
   * Validate that the resolved org actually owns this phone_number_id.
   * Double-check against DB to prevent cache poisoning.
   * This is called on sensitive operations (booking, payment).
   */
  async validateOwnership(phoneNumberId: string, organizationId: string): Promise<boolean> {
    try {
      const org = await orgWhatsAppKeysService.getOrgByPhoneNumberId(phoneNumberId);
      if (!org) {
        logger.warn('Ownership validation failed: no org for phone_number_id', {
          phoneNumberId,
          organizationId,
        });
        return false;
      }

      const valid = org.id === organizationId;
      if (!valid) {
        logger.security('Ownership mismatch detected', {
          phoneNumberId,
          claimedOrgId: organizationId,
          actualOrgId: org.id,
        });
      }

      return valid;
    } catch (error) {
      logger.error('Ownership validation error', error, { phoneNumberId, organizationId });
      return false;
    }
  }
}

export const whatsAppRouter = new WhatsAppRouterService();
