import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

export interface GoogleUserPayload {
  googleId: string;
  email: string;
  name: string;
  profilePhoto: string | null;
  accessToken: string;
  refreshToken: string;
  expiry: number;
}

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  private readonly logger = new Logger(GoogleStrategy.name);

  constructor(private config: ConfigService) {
    super({
      clientID: config.getOrThrow<string>('GOOGLE_CLIENT_ID'),
      clientSecret: config.getOrThrow<string>('GOOGLE_CLIENT_SECRET'),
      callbackURL: config.getOrThrow<string>('GOOGLE_CALLBACK_URL'),
      scope: ['email', 'profile', 'https://www.googleapis.com/auth/drive.file'],
      accessType: 'offline',
      prompt: 'consent',
    });

    this.patchTokenExchange();
  }

  /**
   * Replace the oauth library's getOAuthAccessToken with a fetch-based
   * implementation to avoid TLS socket errors from the legacy `oauth` package.
   */
  private patchTokenExchange() {
    const oauth2 = (this as any)._oauth2;
    const clientId = oauth2._clientId;
    const clientSecret = oauth2._clientSecret;
    const tokenUrl = oauth2._baseSite + oauth2._accessTokenUrl;
    const logger = this.logger;

    logger.log(`Patched token exchange → ${tokenUrl}`);

    oauth2.getOAuthAccessToken = (
      code: string,
      params: Record<string, string>,
      callback: (err: any, accessToken?: string, refreshToken?: string, params?: any) => void,
    ) => {
      logger.log(`getOAuthAccessToken called, code length: ${code?.length}`);
      params = params || {};
      params['client_id'] = clientId;
      params['client_secret'] = clientSecret;

      const codeParam =
        params.grant_type === 'refresh_token' ? 'refresh_token' : 'code';
      params[codeParam] = code;

      const body = new URLSearchParams(params).toString();

      const doRequest = async (attempt: number): Promise<void> => {
        try {
          logger.log(`Token exchange attempt ${attempt} to ${tokenUrl}`);
          const res = await fetch(tokenUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body,
          });

          const text = await res.text();
          logger.log(`Token exchange response: ${res.status}`);

          if (!res.ok) {
            logger.error(
              `Token exchange failed (attempt ${attempt}): ${res.status} ${text}`,
            );
            callback({ statusCode: res.status, data: text });
            return;
          }

          const data = JSON.parse(text);
          logger.log(
            `Token exchange success: access_token=${!!data.access_token}, refresh_token=${!!data.refresh_token}`,
          );
          callback(
            null,
            data.access_token,
            data.refresh_token,
            data,
          );
        } catch (err: any) {
          if (attempt < 3) {
            logger.warn(
              `Token exchange network error (attempt ${attempt}), retrying: ${err.message}`,
            );
            await new Promise((r) => setTimeout(r, 1000));
            return doRequest(attempt + 1);
          }
          logger.error(`Token exchange failed after retries: ${err.message}`);
          callback(err);
        }
      };

      doRequest(1);
    };
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: {
      id: string;
      emails?: { value: string }[];
      displayName?: string;
      photos?: { value: string }[];
    },
    done: VerifyCallback,
  ): Promise<void> {
    this.logger.log(
      `validate() called — email=${profile.emails?.[0]?.value}, hasAccessToken=${!!accessToken}`,
    );
    const expiry = Math.floor(Date.now() / 1000) + 3600;
    const payload: GoogleUserPayload = {
      googleId: profile.id,
      email: profile.emails?.[0]?.value ?? '',
      name: profile.displayName ?? '',
      profilePhoto: profile.photos?.[0]?.value ?? null,
      accessToken,
      refreshToken: refreshToken ?? '',
      expiry,
    };
    done(null, payload);
  }
}
