/**
 * OAuth 2.0 authentication for Google Search Console
 * Supports interactive browser flow with token persistence
 */

import { OAuth2Client } from 'google-auth-library';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
import open from 'open';
import { createServer } from 'http';

const SCOPES = [
  'https://www.googleapis.com/auth/webmasters',
  'https://www.googleapis.com/auth/webmasters.readonly',
  'https://www.googleapis.com/auth/indexing'
];

const TOKEN_DIR = join(homedir(), '.gsc-mcp');
const TOKEN_PATH = join(TOKEN_DIR, 'oauth-token.json');
const REDIRECT_URI = 'http://localhost:3000/oauth2callback';

export interface OAuthConfig {
  clientId: string;
  clientSecret: string;
  redirectUri?: string;
}

/**
 * Load OAuth credentials from environment or file
 */
export function loadOAuthConfig(): OAuthConfig | null {
  // Try environment variables first
  if (process.env.GSC_OAUTH_CLIENT_ID && process.env.GSC_OAUTH_CLIENT_SECRET) {
    return {
      clientId: process.env.GSC_OAUTH_CLIENT_ID,
      clientSecret: process.env.GSC_OAUTH_CLIENT_SECRET,
      redirectUri: process.env.GSC_OAUTH_REDIRECT_URI || REDIRECT_URI
    };
  }

  // Try loading from file
  if (process.env.GSC_OAUTH_CLIENT_FILE) {
    try {
      const content = readFileSync(process.env.GSC_OAUTH_CLIENT_FILE, 'utf-8');
      const data = JSON.parse(content);
      
      // Support both installed app and web app formats
      const credentials = data.installed || data.web;
      if (!credentials) {
        throw new Error('Invalid OAuth client file format');
      }

      return {
        clientId: credentials.client_id,
        clientSecret: credentials.client_secret,
        redirectUri: credentials.redirect_uris?.[0] || REDIRECT_URI
      };
    } catch (error: any) {
      throw new Error(`Failed to load OAuth config from file: ${error.message}`);
    }
  }

  return null;
}

/**
 * Load stored OAuth tokens from disk
 */
function loadStoredTokens(): any | null {
  if (!existsSync(TOKEN_PATH)) {
    return null;
  }

  try {
    const content = readFileSync(TOKEN_PATH, 'utf-8');
    return JSON.parse(content);
  } catch (error) {
    return null;
  }
}

/**
 * Save OAuth tokens to disk
 */
function saveTokens(tokens: any): void {
  if (!existsSync(TOKEN_DIR)) {
    mkdirSync(TOKEN_DIR, { recursive: true });
  }

  writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2));
}

/**
 * Perform OAuth flow with browser-based consent
 */
async function performOAuthFlow(oauth2Client: OAuth2Client): Promise<void> {
  const authUrl = oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
    prompt: 'consent' // Force consent to get refresh token
  });

  console.error('Opening browser for Google authentication...');
  console.error('If browser does not open, visit this URL:');
  console.error(authUrl);

  // Create temporary HTTP server to capture callback
  const server = createServer();
  const port = 3000;

  const tokenPromise = new Promise<any>((resolve, reject) => {
    server.on('request', async (req, res) => {
      try {
        const url = new URL(req.url || '', `http://localhost:${port}`);
        
        if (url.pathname === '/oauth2callback') {
          const code = url.searchParams.get('code');
          
          if (!code) {
            res.writeHead(400);
            res.end('Missing authorization code');
            reject(new Error('Missing authorization code'));
            return;
          }

          res.writeHead(200, { 'Content-Type': 'text/html' });
          res.end(`
            <html>
              <body>
                <h1>Authentication successful!</h1>
                <p>You can close this window and return to the terminal.</p>
              </body>
            </html>
          `);

          // Exchange code for tokens
          const { tokens } = await oauth2Client.getToken(code);
          resolve(tokens);
          server.close();
        }
      } catch (error) {
        reject(error);
        server.close();
      }
    });

    server.on('error', (error) => {
      reject(error);
    });
  });

  server.listen(port);

  // Open browser
  await open(authUrl);

  // Wait for tokens
  const tokens = await tokenPromise;
  oauth2Client.setCredentials(tokens);
  saveTokens(tokens);
}

/**
 * Create authenticated OAuth2 client
 */
export async function getOAuthClient(): Promise<OAuth2Client> {
  const config = loadOAuthConfig();
  
  if (!config) {
    throw new Error(
      'OAuth credentials not found. Set GSC_OAUTH_CLIENT_ID/SECRET or GSC_OAUTH_CLIENT_FILE'
    );
  }

  const oauth2Client = new OAuth2Client(
    config.clientId,
    config.clientSecret,
    config.redirectUri
  );

  // Try to load stored tokens
  const storedTokens = loadStoredTokens();
  
  if (storedTokens) {
    oauth2Client.setCredentials(storedTokens);
    
    // Set up automatic token refresh
    oauth2Client.on('tokens', (tokens) => {
      if (tokens.refresh_token) {
        saveTokens({ ...storedTokens, ...tokens });
      } else {
        saveTokens({ ...storedTokens, access_token: tokens.access_token });
      }
    });

    // Verify tokens are still valid
    try {
      await oauth2Client.getAccessToken();
      return oauth2Client;
    } catch (error) {
      console.error('Stored tokens invalid, re-authenticating...');
    }
  }

  // Perform interactive OAuth flow
  await performOAuthFlow(oauth2Client);
  
  // Set up automatic token refresh
  oauth2Client.on('tokens', (tokens) => {
    const current = loadStoredTokens() || {};
    saveTokens({ ...current, ...tokens });
  });

  return oauth2Client;
}
