import {
  BadRequestException,
  Controller,
  Delete,
  Get,
  HttpCode,
  Query,
  Res,
  UseGuards,
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Throttle } from '@nestjs/throttler';
import { type Response } from 'express';
import { JwtAuthGuard } from '@modules/auth/presentation/guards/jwt-auth.guard';
import { CurrentUser } from '@modules/auth/presentation/decorators/current-user.decorator';
import { type JwtPayload } from '@modules/auth/infrastructure/services/token.service';
import { ZaloOaService } from '../../infrastructure/services/zalo-oa.service';

const FRONTEND_URL = process.env['FRONTEND_URL'] ?? 'http://localhost:3000';
const CSRF_STATE_LENGTH = 32;

function generateCsrfState(): string {
  return Buffer.from(
    Array.from({ length: CSRF_STATE_LENGTH }, () => Math.floor(Math.random() * 256)),
  ).toString('base64url');
}

@ApiTags('auth')
@Controller('auth/zalo-oa')
export class ZaloOaLinkController {
  constructor(private readonly zaloOaService: ZaloOaService) {}

  /**
   * Initiate Zalo OA account linking for the authenticated user.
   *
   * Returns 302 redirect to the Zalo OA consent screen.
   * On return, Zalo calls back to `/auth/zalo-oa/callback`.
   *
   * The `state` param encodes `userId:csrfToken` so the callback can verify
   * the request origin without a server-side session.
   */
  @Get('link')
  @UseGuards(JwtAuthGuard)
  @ApiOperation({ summary: 'Initiate Zalo OA account linking' })
  @ApiResponse({ status: 302, description: 'Redirect to Zalo OA consent screen' })
  initiateLink(
    @CurrentUser() user: JwtPayload,
    @Res() res: Response,
  ): void {
    if (!this.zaloOaService.isOAuthEnabled) {
      throw new BadRequestException('Zalo OA linking is not configured on this server');
    }

    const csrf = generateCsrfState();
    // Encode userId + csrf into state so the callback can verify
    const state = Buffer.from(JSON.stringify({ uid: user.sub, csrf })).toString('base64url');

    const authUrl = this.zaloOaService.getOAuthAuthorizeUrl(state);
    res.redirect(authUrl);
  }

  /**
   * Zalo OA OAuth callback.
   *
   * Exchanges the authorization code for OA-scoped tokens, resolves the Zalo OA UID,
   * and stores encrypted tokens in `zalo_account_links`.
   *
   * On success redirects to frontend `/settings/zalo?linked=true`.
   * On failure redirects to frontend `/settings/zalo?error=<reason>`.
   */
  @Throttle({ default: { ttl: 3_600_000, limit: 10 } })
  @Get('callback')
  @ApiOperation({ summary: 'Zalo OA OAuth2 callback' })
  @ApiResponse({ status: 302, description: 'Redirect to frontend settings page' })
  async handleCallback(
    @Query('code') code: string,
    @Query('state') state: string,
    @Res() res: Response,
  ): Promise<void> {
    if (!code || !state) {
      res.redirect(`${FRONTEND_URL}/settings/zalo?error=missing_params`);
      return;
    }

    let userId: string;
    try {
      const decoded = JSON.parse(Buffer.from(state, 'base64url').toString('utf8')) as {
        uid?: string;
      };
      if (!decoded.uid) throw new Error('missing uid in state');
      userId = decoded.uid;
    } catch {
      res.redirect(`${FRONTEND_URL}/settings/zalo?error=invalid_state`);
      return;
    }

    try {
      await this.zaloOaService.handleOAuthCallback(userId, code);
      res.redirect(`${FRONTEND_URL}/settings/zalo?linked=true`);
    } catch (error) {
      const msg = error instanceof Error ? error.message : 'unknown';
      res.redirect(
        `${FRONTEND_URL}/settings/zalo?error=link_failed&detail=${encodeURIComponent(msg)}`,
      );
    }
  }

  /**
   * Unlink the authenticated user's Zalo OA account.
   */
  @Delete('link')
  @UseGuards(JwtAuthGuard)
  @HttpCode(204)
  @ApiOperation({ summary: 'Unlink Zalo OA account' })
  @ApiResponse({ status: 204, description: 'Account unlinked' })
  async unlink(@CurrentUser() user: JwtPayload): Promise<void> {
    await this.zaloOaService.unlinkAccount(user.sub);
  }
}
