import { Body, Controller, Get, HttpCode, Post, Query, RawBodyRequest, Req } from '@nestjs/common';
import type { Request } from 'express';
import { LoggerService, PrismaService } from '@modules/shared';
import { ZaloOaService } from '../../infrastructure/services/zalo-oa.service';

/**
 * Zalo OA event types from webhook payloads.
 *
 * @see https://developers.zalo.me/docs/official-account/webhook
 */
interface ZaloOaWebhookPayload {
  app_id: string;
  event_name: string;
  timestamp: string;
  sender: { id: string };
  recipient: { id: string };
  message?: { text?: string; msg_id?: string; attachments?: unknown[] };
  follower?: { id: string };
  user_id_by_app?: string;
}

const WEBHOOK_CONTEXT = 'ZaloOaWebhookController';

@Controller('webhooks/zalo-oa')
export class ZaloOaWebhookController {
  constructor(
    private readonly prisma: PrismaService,
    private readonly logger: LoggerService,
    private readonly zaloOaService: ZaloOaService,
  ) {}

  /**
   * Webhook verification endpoint.
   * Zalo OA sends a GET request with a challenge token during webhook setup.
   */
  @Get()
  verify(@Query('challenge') challenge: string): string {
    this.logger.log(`Webhook verification: challenge=${challenge}`, WEBHOOK_CONTEXT);
    return challenge ?? '';
  }

  /**
   * Receive and process Zalo OA webhook events.
   *
   * Supported events:
   * - `follow` — user follows the OA; records interaction + checks existing link
   * - `unfollow` — user unfollows the OA
   * - `user_send_text` — user sends a text message; records interaction
   */
  @Post()
  @HttpCode(200)
  async handleEvent(
    @Body() payload: ZaloOaWebhookPayload,
    @Req() req: RawBodyRequest<Request>,
  ): Promise<{ received: true }> {
    const { event_name, sender, timestamp } = payload;

    this.logger.log(
      `Webhook event: ${event_name} from=${sender?.id ?? 'unknown'} at=${timestamp}`,
      WEBHOOK_CONTEXT,
    );

    // Accept webhooks regardless of which mode is active
    if (!this.zaloOaService.isAvailable && !this.zaloOaService.isOAuthEnabled) {
      this.logger.warn('Zalo OA not configured — ignoring webhook event', WEBHOOK_CONTEXT);
      return { received: true };
    }

    try {
      switch (event_name) {
        case 'follow':
          await this.handleFollow(payload);
          break;
        case 'unfollow':
          await this.handleUnfollow(payload);
          break;
        case 'user_send_text':
          await this.handleUserMessage(payload);
          break;
        default:
          this.logger.log(`Unhandled event type: ${event_name}`, WEBHOOK_CONTEXT);
      }
    } catch (error) {
      this.logger.error(
        `Webhook processing failed for ${event_name}: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        WEBHOOK_CONTEXT,
      );
    }

    return { received: true };
  }

  /**
   * Handle `follow` event — record interaction (opens 24-hour ZNS window)
   * and log link status.
   */
  private async handleFollow(payload: ZaloOaWebhookPayload): Promise<void> {
    const zaloUid = payload.sender?.id ?? payload.follower?.id;
    if (!zaloUid) return;

    // Record interaction so the 24-hour window opens for ZNS sends
    await this.zaloOaService.recordInteraction(zaloUid);

    // Check OA account-links table first
    const oaLink = await this.prisma.zaloAccountLink.findFirst({
      where: { zaloUserId: zaloUid },
    });

    if (oaLink) {
      this.logger.log(
        `Follow event: Zalo UID ${zaloUid.slice(0, 6)}*** already OA-linked to user ${oaLink.userId}`,
        WEBHOOK_CONTEXT,
      );
      return;
    }

    // Legacy: check OAuthAccount
    const existingOAuth = await this.prisma.oAuthAccount.findFirst({
      where: { provider: 'ZALO', providerUserId: zaloUid },
    });

    if (existingOAuth) {
      this.logger.log(
        `Follow event: Zalo UID ${zaloUid.slice(0, 6)}*** linked via social OAuth to user ${existingOAuth.userId}`,
        WEBHOOK_CONTEXT,
      );
      return;
    }

    this.logger.log(
      `Follow event: Zalo UID ${zaloUid.slice(0, 6)}*** — no existing link found. User should complete OA linking via /auth/zalo-oa/link.`,
      WEBHOOK_CONTEXT,
    );
  }

  /**
   * Handle `unfollow` event — log for analytics.
   * We do NOT remove the OA link (user may re-follow and still want notifications).
   */
  private async handleUnfollow(payload: ZaloOaWebhookPayload): Promise<void> {
    const zaloUid = payload.sender?.id;
    if (!zaloUid) return;

    this.logger.log(
      `Unfollow event: Zalo UID ${zaloUid.slice(0, 6)}*** unfollowed OA`,
      WEBHOOK_CONTEXT,
    );
  }

  /**
   * Handle incoming text message from a Zalo user.
   * Records the interaction (refreshes the 24-hour ZNS window) and logs for routing.
   */
  private async handleUserMessage(payload: ZaloOaWebhookPayload): Promise<void> {
    const zaloUid = payload.sender?.id;
    const text = payload.message?.text;
    const msgId = payload.message?.msg_id;

    if (!zaloUid || !text) return;

    // Record interaction so the ZNS send window stays open
    await this.zaloOaService.recordInteraction(zaloUid);

    this.logger.log(
      `Message from Zalo UID ${zaloUid.slice(0, 6)}***: msgId=${msgId ?? 'unknown'} length=${text.length}`,
      WEBHOOK_CONTEXT,
    );

    // Find linked user via OA account-links
    const oaLink = await this.prisma.zaloAccountLink.findFirst({
      where: { zaloUserId: zaloUid },
      select: { userId: true },
    });

    if (oaLink) {
      this.logger.log(
        `Message from OA-linked user ${oaLink.userId} via Zalo OA`,
        WEBHOOK_CONTEXT,
      );
    }
  }
}
