import * as fs from 'fs';
import * as crypto from 'crypto';
import axios from 'axios';

/**
 * Optimus Authentication Client
 * Handles authentication for API requests to Optimus endpoints
 */
export class OptimusAuth {
  private keyId: string;
  private token: string;
  private baseUrl: string;

  /**
   * Create a new OptimusAuth client
   * @param envPath Path to the optimus.env file
   * @param baseUrl Base URL for the API (optional, will use OPTIMUS_ENDPOINT from env file if not provided)
   */
  constructor(envPath: string = './optimus.env', baseUrl?: string) {
    // Load credentials from env file
    const envVars = this.loadEnvFile(envPath);
    this.keyId = envVars.OPTIMUS_KEY_ID;
    this.token = envVars.OPTIMUS_TOKEN;
    
    // Use provided baseUrl or extract from OPTIMUS_ENDPOINT
    if (baseUrl) {
      this.baseUrl = baseUrl;
    } else if (envVars.OPTIMUS_ENDPOINT) {
      // Extract base URL from OPTIMUS_ENDPOINT (remove /optimus_security/ suffix)
      this.baseUrl = envVars.OPTIMUS_ENDPOINT.replace(/\/optimus_security\/?$/, '');
    } else {
      throw new Error('Missing OPTIMUS_ENDPOINT in env file and no baseUrl provided');
    }
    
    if (!this.keyId || !this.token) {
      throw new Error('Missing OPTIMUS_KEY_ID or OPTIMUS_TOKEN in env file');
    }
  }

  /**
   * Load environment variables from file
   */
  private loadEnvFile(filePath: string): Record<string, string> {
    try {
      const content = fs.readFileSync(filePath, 'utf8');
      const result: Record<string, string> = {};
      
      content.split('\n').forEach(line => {
        line = line.trim();
        if (line && !line.startsWith('#')) {
          const [key, value] = line.split('=', 2);
          if (key && value) {
            result[key.trim()] = value.trim();
          }
        }
      });
      
      return result;
    } catch (err) {
      console.error('Error loading env file:', err);
      throw new Error(`Could not load env file: ${err}`);
    }
  }

  /**
   * Generate authentication signature
   */
  private generateSignature(timestamp: string, payload: any): string {
    // Convert payload to string if needed
    let payloadStr: string;
    if (typeof payload === 'string') {
      payloadStr = payload;
    } else {
      // Create a sorted object for consistent signature generation
      const sortedPayload: any = {};
      Object.keys(payload).sort().forEach(key => {
        sortedPayload[key] = payload[key];
      });
      // Match Python's json.dumps() format exactly (with spaces after colons)
      payloadStr = JSON.stringify(sortedPayload).replace(/":/g, '": ');
    }
    
    // Create the string to sign
    const stringToSign = `${timestamp}:${payloadStr}`;
    
    // Generate HMAC-SHA256 signature
    return crypto
      .createHmac('sha256', this.token)
      .update(stringToSign)
      .digest('hex');
  }

  /**
   * Get authentication headers
   * @param payload Request payload (for signature generation)
   * @returns Authentication headers
   */
  public getAuthHeaders(payload: any = {}): Record<string, string> {
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const signature = this.generateSignature(timestamp, payload);
    
    return {
      'X-Key-ID': this.keyId,
      'X-Signature': signature,
      'X-Timestamp': timestamp
    };
  }

  /**
   * Make authenticated GET request
   * @param endpoint API endpoint path (will be appended to baseUrl)
   * @param params Query parameters
   * @returns Response data
   */
  public async get<T = any>(endpoint: string, params: Record<string, any> = {}): Promise<T> {
    try {
      const url = `${this.baseUrl}${endpoint}`;
      const headers = this.getAuthHeaders(params);
      
      const response = await axios.get(url, { headers, params });
      return response.data;
    } catch (err) {
      if (axios.isAxiosError(err)) {
        throw new Error(`API request failed: ${err.response?.status} ${err.response?.statusText}`);
      }
      throw err;
    }
  }

  /**
   * Make authenticated POST request
   * @param endpoint API endpoint path
   * @param data Request body
   * @returns Response data
   */
  public async post<T = any>(endpoint: string, data: any = {}): Promise<T> {
    try {
      const url = `${this.baseUrl}${endpoint}`;
      const headers = this.getAuthHeaders(data);
      
      const response = await axios.post(url, data, { headers });
      return response.data;
    } catch (err) {
      if (axios.isAxiosError(err)) {
        throw new Error(`API request failed: ${err.response?.status} ${err.response?.statusText}`);
      }
      throw err;
    }
  }

  /**
   * Download a file from an authenticated endpoint
   * @param endpoint API endpoint path
   * @param outputPath Path to save the file
   * @param params Query parameters
   */
  public async downloadFile(endpoint: string, outputPath: string, params: Record<string, any> = {}): Promise<void> {
    try {
      const url = `${this.baseUrl}${endpoint}`;
      const headers = this.getAuthHeaders(params);
      
      const response = await axios.get(url, { 
        headers, 
        params,
        responseType: 'stream'
      });
      
      // Ensure directory exists
      const dir = outputPath.substring(0, outputPath.lastIndexOf('/'));
      if (dir && !fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
      }
      
      // Save the file
      const writer = fs.createWriteStream(outputPath);
      response.data.pipe(writer);
      
      return new Promise((resolve, reject) => {
        writer.on('finish', resolve);
        writer.on('error', reject);
      });
    } catch (err) {
      if (axios.isAxiosError(err)) {
        throw new Error(`File download failed: ${err.response?.status} ${err.response?.statusText}`);
      }
      throw err;
    }
  }

  /**
   * Download a file from an authenticated endpoint with a specific timestamp
   * @param endpoint API endpoint path
   * @param outputPath Path to save the file
   * @param params Query parameters
   * @param timestamp Specific timestamp to use for authentication
   */
  public async downloadFileWithTimestamp(endpoint: string, outputPath: string, params: Record<string, any> = {}, timestamp: string): Promise<void> {
    try {
      const url = `${this.baseUrl}${endpoint}`;
      const signature = this.generateSignature(timestamp, params);
      
      const headers = {
        'X-Key-ID': this.keyId,
        'X-Signature': signature,
        'X-Timestamp': timestamp
      };
      
      const response = await axios.get(url, { 
        headers, 
        params,
        responseType: 'stream'
      });
      
      // Ensure directory exists
      const dir = outputPath.substring(0, outputPath.lastIndexOf('/'));
      if (dir && !fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
      }
      
      // Save the file
      const writer = fs.createWriteStream(outputPath);
      response.data.pipe(writer);
      
      return new Promise((resolve, reject) => {
        writer.on('finish', resolve);
        writer.on('error', reject);
      });
    } catch (err) {
      if (axios.isAxiosError(err)) {
        throw new Error(`File download failed: ${err.response?.status} ${err.response?.statusText}`);
      }
      throw err;
    }
  }
}