/**
 * TextMe Web Authentication Module
 * Handles QR code authentication, JWT token management, and session handling
 */

import { randomUUID } from 'crypto';
import WebSocket from 'ws';

// Types
export interface AuthTokens {
  token: string;
  refreshToken?: string;
  expiresAt?: number;
}

export interface QRAuthMessage {
  type: 'auth_success' | 'auth_failed' | 'session_expired' | 'ping';
  token?: string;
  error?: string;
}

export interface TokenRefreshResponse {
  token: string;
  expires_in?: number;
}

export class TextMeAuthError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode?: number
  ) {
    super(message);
    this.name = 'TextMeAuthError';
  }
}

// Constants
const BASE_URL = 'https://api.textme-app.com';
const WS_BASE_URL = 'wss://webauth.textme-app.com/ws';
const SESSION_PREFIX = 'TMWEB';
const QR_AUTH_TIMEOUT_MS = 120000; // 2 minutes for QR scan

export class TextMeAuth {
  private token: string | null = null;
  private csrfToken: string | null = null;
  private tokenExpiresAt: number | null = null;
  private activeWebSocket: WebSocket | null = null;

  constructor(
    private baseUrl: string = BASE_URL,
    private wsBaseUrl: string = WS_BASE_URL
  ) {}

  /**
   * Generate a new QR session ID for authentication
   * Format: TMWEB-{uuid}
   */
  generateQRSession(): string {
    const uuid = randomUUID();
    return `${SESSION_PREFIX}-${uuid}`;
  }

  /**
   * Wait for QR code authentication via WebSocket
   * User scans QR with mobile app, server sends JWT token
   */
  async waitForQRAuth(sessionId: string): Promise<string> {
    return new Promise((resolve, reject) => {
      const wsUrl = `${this.wsBaseUrl}/${sessionId}/`;
      
      try {
        this.activeWebSocket = new WebSocket(wsUrl);
      } catch (err) {
        reject(new TextMeAuthError(
          `Failed to create WebSocket connection: ${err}`,
          'WS_CONNECTION_FAILED'
        ));
        return;
      }

      const timeout = setTimeout(() => {
        this.closeWebSocket();
        reject(new TextMeAuthError(
          'QR authentication timed out',
          'QR_AUTH_TIMEOUT'
        ));
      }, QR_AUTH_TIMEOUT_MS);

      this.activeWebSocket.on('open', () => {
        console.log(`[TextMeAuth] WebSocket connected for session: ${sessionId}`);
      });

      this.activeWebSocket.on('message', (data: WebSocket.Data) => {
        try {
          const message: QRAuthMessage = JSON.parse(data.toString());

          switch (message.type) {
            case 'auth_success':
              clearTimeout(timeout);
              if (message.token) {
                this.token = message.token;
                this.tokenExpiresAt = Date.now() + (24 * 60 * 60 * 1000); // Default 24h
                this.closeWebSocket();
                resolve(message.token);
              } else {
                this.closeWebSocket();
                reject(new TextMeAuthError(
                  'Auth success but no token received',
                  'NO_TOKEN'
                ));
              }
              break;

            case 'auth_failed':
              clearTimeout(timeout);
              this.closeWebSocket();
              reject(new TextMeAuthError(
                message.error || 'Authentication failed',
                'AUTH_FAILED'
              ));
              break;

            case 'session_expired':
              clearTimeout(timeout);
              this.closeWebSocket();
              reject(new TextMeAuthError(
                'QR session expired',
                'SESSION_EXPIRED'
              ));
              break;

            case 'ping':
              // Keep-alive, respond with pong if needed
              this.activeWebSocket?.send(JSON.stringify({ type: 'pong' }));
              break;

            default:
              console.log(`[TextMeAuth] Unknown message type: ${(message as any).type}`);
          }
        } catch (err) {
          console.error('[TextMeAuth] Failed to parse WebSocket message:', err);
        }
      });

      this.activeWebSocket.on('error', (err) => {
        clearTimeout(timeout);
        this.closeWebSocket();
        reject(new TextMeAuthError(
          `WebSocket error: ${err.message}`,
          'WS_ERROR'
        ));
      });

      this.activeWebSocket.on('close', (code, reason) => {
        console.log(`[TextMeAuth] WebSocket closed: ${code} - ${reason}`);
      });
    });
  }

  /**
   * Refresh the current JWT token
   * POST to /api/auth-token-refresh/ with current token
   */
  async refreshToken(): Promise<string> {
    if (!this.token) {
      throw new TextMeAuthError(
        'No token to refresh',
        'NO_TOKEN'
      );
    }

    const url = `${this.baseUrl}/api/auth-token-refresh/`;
    
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      'Authorization': `JWT ${this.token}`,
    };

    // Include CSRF token if available (Django requirement)
    if (this.csrfToken) {
      headers['X-CSRFToken'] = this.csrfToken;
    }

    try {
      const response = await fetch(url, {
        method: 'POST',
        headers,
        body: JSON.stringify({ token: this.token }),
        credentials: 'include', // Include cookies for CSRF
      });

      // Extract CSRF token from response cookies if present
      const setCookie = response.headers.get('set-cookie');
      if (setCookie) {
        this.extractCSRFToken(setCookie);
      }

      if (!response.ok) {
        const errorText = await response.text();
        throw new TextMeAuthError(
          `Token refresh failed: ${errorText}`,
          'REFRESH_FAILED',
          response.status
        );
      }

      const data = await response.json() as TokenRefreshResponse;
      
      this.token = data.token;
      this.tokenExpiresAt = data.expires_in 
        ? Date.now() + (data.expires_in * 1000)
        : Date.now() + (24 * 60 * 60 * 1000);

      return this.token;
    } catch (err) {
      if (err instanceof TextMeAuthError) {
        throw err;
      }
      throw new TextMeAuthError(
        `Token refresh request failed: ${err}`,
        'NETWORK_ERROR'
      );
    }
  }

  /**
   * Get authentication headers for API calls
   * Returns Authorization and CSRF headers
   */
  getAuthHeaders(): Record<string, string> {
    const headers: Record<string, string> = {};

    if (this.token) {
      headers['Authorization'] = `JWT ${this.token}`;
    }

    if (this.csrfToken) {
      headers['X-CSRFToken'] = this.csrfToken;
    }

    return headers;
  }

  /**
   * Check if currently authenticated with a valid token
   */
  isAuthenticated(): boolean {
    if (!this.token) {
      return false;
    }

    // Check if token is expired (with 5 min buffer)
    if (this.tokenExpiresAt) {
      const bufferMs = 5 * 60 * 1000; // 5 minutes
      return Date.now() < (this.tokenExpiresAt - bufferMs);
    }

    return true;
  }

  /**
   * Clear authentication state and logout
   */
  logout(): void {
    this.token = null;
    this.csrfToken = null;
    this.tokenExpiresAt = null;
    this.closeWebSocket();
    console.log('[TextMeAuth] Logged out');
  }

  /**
   * Set CSRF token manually (useful when extracted from page/cookie)
   */
  setCSRFToken(token: string): void {
    this.csrfToken = token;
  }

  /**
   * Get the current token (for storage/restoration)
   */
  getToken(): string | null {
    return this.token;
  }

  /**
   * Restore a previously saved token
   */
  restoreToken(token: string, expiresAt?: number): void {
    this.token = token;
    this.tokenExpiresAt = expiresAt || null;
  }

  /**
   * Extract CSRF token from Django-style cookie header
   */
  private extractCSRFToken(cookieHeader: string): void {
    const csrfMatch = cookieHeader.match(/csrftoken=([^;]+)/);
    if (csrfMatch) {
      this.csrfToken = csrfMatch[1];
    }
  }

  /**
   * Close active WebSocket connection
   */
  private closeWebSocket(): void {
    if (this.activeWebSocket) {
      try {
        this.activeWebSocket.close();
      } catch (err) {
        // Ignore close errors
      }
      this.activeWebSocket = null;
    }
  }
}

export default TextMeAuth;
