import { Injectable } from '@nestjs/common';
import { LoggerService } from '@modules/shared';
import { OAuthService, type OAuthUserProfile } from '../services/oauth.service';
import { type TokenPair } from '../services/token.service';

/**
 * Zalo OAuth2 integration.
 *
 * Zalo does not have a passport strategy npm package, so this service
 * handles the OAuth2 flow directly:
 *   1. Generate authorization URL → redirect user
 *   2. Exchange authorization code for access token
 *   3. Fetch user profile from Zalo Graph API
 *   4. Delegate to OAuthService for account linking/creation
 *
 * Zalo OAuth2 endpoints:
 *   - Authorization: https://oauth.zaloapp.com/v4/permission
 *   - Token:        https://oauth.zaloapp.com/v4/access_token
 *   - User Info:    https://graph.zalo.me/v2.0/me
 */

interface ZaloTokenResponse {
  access_token: string;
  refresh_token?: string;
  expires_in: number;
  error?: number;
  error_description?: string;
}

interface ZaloUserInfo {
  id: string;
  name: string;
  picture?: { data?: { url?: string } };
  error?: number;
  message?: string;
}

@Injectable()
export class ZaloOAuthStrategy {
  private readonly appId: string;
  private readonly appSecret: string;
  private readonly callbackUrl: string;

  constructor(
    private readonly oauthService: OAuthService,
    private readonly logger: LoggerService,
  ) {
    const appId = process.env['ZALO_APP_ID'];
    const appSecret = process.env['ZALO_APP_SECRET'];

    if (!appId || !appSecret) {
      // Allow app to start without Zalo OAuth configured — routes will be non-functional.
      this.appId = 'NOT_CONFIGURED';
      this.appSecret = 'NOT_CONFIGURED';
      this.callbackUrl = process.env['ZALO_CALLBACK_URL'] ?? '/auth/zalo/callback';
      return;
    }

    this.appId = appId;
    this.appSecret = appSecret;
    this.callbackUrl = process.env['ZALO_CALLBACK_URL'] ?? '/auth/zalo/callback';
  }

  /**
   * Generate Zalo authorization URL for redirecting the user.
   */
  getAuthorizationUrl(state?: string): string {
    const params = new URLSearchParams({
      app_id: this.appId,
      redirect_uri: this.callbackUrl,
      state: state ?? '',
    });
    return `https://oauth.zaloapp.com/v4/permission?${params.toString()}`;
  }

  /**
   * Handle the OAuth callback: exchange code for tokens, fetch profile,
   * and authenticate via OAuthService.
   */
  async handleCallback(code: string, codeVerifier?: string): Promise<{ tokens: TokenPair }> {
    // 1. Exchange authorization code for access token
    const tokenData = await this.exchangeCode(code, codeVerifier);

    // 2. Fetch user profile
    const userInfo = await this.fetchUserInfo(tokenData.access_token);

    // 3. Build OAuthUserProfile and authenticate
    const expiresAt = new Date();
    expiresAt.setSeconds(expiresAt.getSeconds() + tokenData.expires_in);

    const oauthProfile: OAuthUserProfile = {
      provider: 'ZALO',
      providerUserId: userInfo.id,
      fullName: userInfo.name || 'Zalo User',
      avatarUrl: userInfo.picture?.data?.url,
      accessToken: tokenData.access_token,
      refreshToken: tokenData.refresh_token,
      expiresAt,
      rawProfile: userInfo as unknown as Record<string, unknown>,
    };

    const tokens = await this.oauthService.authenticateOAuth(oauthProfile);
    return { tokens };
  }

  private async exchangeCode(code: string, codeVerifier?: string): Promise<ZaloTokenResponse> {
    const body = new URLSearchParams({
      code,
      app_id: this.appId,
      grant_type: 'authorization_code',
    });
    if (codeVerifier) {
      body.set('code_verifier', codeVerifier);
    }

    const response = await fetch('https://oauth.zaloapp.com/v4/access_token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'secret_key': this.appSecret,
      },
      body: body.toString(),
    });

    const data = (await response.json()) as ZaloTokenResponse;

    if (data.error) {
      this.logger.error(`Zalo token exchange failed: ${data.error_description ?? data.error}`, undefined, 'ZaloOAuthStrategy');
      throw new Error(`Zalo OAuth error: ${data.error_description ?? 'Token exchange failed'}`);
    }

    if (!data.access_token) {
      throw new Error('Zalo OAuth error: No access token in response');
    }

    return data;
  }

  private async fetchUserInfo(accessToken: string): Promise<ZaloUserInfo> {
    const response = await fetch(
      'https://graph.zalo.me/v2.0/me?fields=id,name,picture',
      {
        headers: {
          'access_token': accessToken,
        },
      },
    );

    const data = (await response.json()) as ZaloUserInfo;

    if (data.error) {
      this.logger.error(`Zalo user info fetch failed: ${data.message ?? data.error}`, undefined, 'ZaloOAuthStrategy');
      throw new Error(`Zalo OAuth error: ${data.message ?? 'Failed to fetch user info'}`);
    }

    if (!data.id) {
      throw new Error('Zalo OAuth error: No user ID in response');
    }

    return data;
  }
}
