#!/usr/bin/env node

/**
 * Query Omada Cloud Controller firewall rules for Blockscout access
 * Uses cloud controller API if credentials are available
 */

import https from 'https';
import { readFileSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';

const BLOCKSCOUT_IP = '192.168.11.140';
const BLOCKSCOUT_PORT = '80';

// Load environment variables
const envPath = join(homedir(), '.env');
let envVars = {};

try {
  const envFile = readFileSync(envPath, 'utf8');
  envFile.split('\n').forEach(line => {
    if (line.includes('=') && !line.trim().startsWith('#')) {
      const [key, ...values] = line.split('=');
      if (key && /^[A-Z_][A-Z0-9_]*$/.test(key.trim())) {
        let value = values.join('=').trim();
        if ((value.startsWith('"') && value.endsWith('"')) || 
            (value.startsWith("'") && value.endsWith("'"))) {
          value = value.slice(1, -1);
        }
        envVars[key.trim()] = value;
      }
    }
  });
} catch (error) {
  console.error('Error loading .env file:', error.message);
  process.exit(1);
}

// Try to detect cloud controller URL
// Omada Cloud Controller typically uses: https://controller.tplinkcloud.com or specific domain
const cloudControllerUrl = envVars.OMADA_CLOUD_CONTROLLER_URL || 
                          envVars.OMADA_CONTROLLER_URL || 
                          envVars.OMADA_CONTROLLER_BASE_URL || 
                          'https://192.168.11.8:8043'; // Fallback to local

// Check if this is a cloud URL (contains tplinkcloud.com or is not a local IP)
const isCloudController = cloudControllerUrl.includes('tplinkcloud.com') || 
                         cloudControllerUrl.includes('cloud') ||
                         (!cloudControllerUrl.match(/^https?:\/\/192\.168\./) && 
                          !cloudControllerUrl.match(/^https?:\/\/10\./) &&
                          !cloudControllerUrl.match(/^https?:\/\/172\.(1[6-9]|2[0-9]|3[01])\./));

const username = envVars.OMADA_ADMIN_USERNAME || envVars.OMADA_API_KEY || envVars.OMADA_CLIENT_ID;
const password = envVars.OMADA_ADMIN_PASSWORD || envVars.OMADA_API_SECRET || envVars.OMADA_CLIENT_SECRET;
const siteId = envVars.OMADA_SITE_ID || '090862bebcb1997bb263eea9364957fe';
const verifySSL = envVars.OMADA_VERIFY_SSL !== 'false';

if (!username || !password) {
  console.error('Error: Missing credentials');
  console.error('Required: OMADA_ADMIN_USERNAME/OMADA_API_KEY and OMADA_ADMIN_PASSWORD/OMADA_API_SECRET');
  process.exit(1);
}

// Parse base URL
const urlObj = new URL(cloudControllerUrl);
const hostname = urlObj.hostname;
const port = urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80);

console.log('════════════════════════════════════════');
console.log('Omada Firewall Rules Check for Blockscout');
console.log(isCloudController ? '(Cloud Controller)' : '(Local Controller)');
console.log('════════════════════════════════════════');
console.log('');
console.log(`Controller URL: ${cloudControllerUrl}`);
console.log(`Controller Type: ${isCloudController ? 'Cloud' : 'Local'}`);
console.log(`Site ID: ${siteId}`);
console.log(`Blockscout IP: ${BLOCKSCOUT_IP}`);
console.log(`Blockscout Port: ${BLOCKSCOUT_PORT}`);
console.log('');

// Create HTTPS agent
const agent = new https.Agent({
  rejectUnauthorized: verifySSL,
});

// Function to make API request
function apiRequest(method, path, data = null, token = null, cookies = null) {
  return new Promise((resolve, reject) => {
    const options = {
      hostname,
      port,
      path,
      method,
      agent,
      headers: {
        'Content-Type': 'application/json',
      },
    };

    if (token) {
      options.headers['Csrf-Token'] = token;
    }

    if (cookies) {
      options.headers['Cookie'] = cookies;
    }

    const req = https.request(options, (res) => {
      let body = '';
      res.on('data', (chunk) => {
        body += chunk;
      });
      res.on('end', () => {
        try {
          const json = JSON.parse(body);
          resolve(json);
        } catch (e) {
          resolve({ raw: body, statusCode: res.statusCode, headers: res.headers });
        }
      });
    });

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

    if (data) {
      req.write(JSON.stringify(data));
    }
    req.end();
  });
}

async function main() {
  try {
    console.log('1. Authenticating to Omada Controller...');
    
    // Try login endpoint
    let loginResponse;
    try {
      loginResponse = await apiRequest('POST', '/api/v2/login', {
        username,
        password,
      });
    } catch (error) {
      console.error(`   ✗ Login failed: ${error.message}`);
      console.error('');
      console.error('Note: Cloud controllers may use different authentication endpoints.');
      console.error('Please check Omada Controller documentation for cloud API endpoints.');
      process.exit(1);
    }
    
    if (loginResponse.errorCode !== 0) {
      console.error(`   ✗ Login failed: ${loginResponse.msg || 'Unknown error'}`);
      console.error(`   Error Code: ${loginResponse.errorCode}`);
      
      // If cloud controller, suggest alternative authentication
      if (isCloudController) {
        console.error('');
        console.error('Cloud Controller Authentication Notes:');
        console.error('  - Cloud controllers may require different authentication');
        console.error('  - May need to use OAuth token endpoint instead of /api/v2/login');
        console.error('  - Check Omada Cloud Controller documentation');
      }
      
      process.exit(1);
    }

    const token = loginResponse.result?.token || loginResponse.token;
    if (!token) {
      console.error('   ✗ No token received');
      process.exit(1);
    }

    console.log('   ✓ Login successful');
    console.log('');

    // Build cookie string for subsequent requests
    const cookies = `TOKEN=${token}`;
    const effectiveSiteId = siteId;

    console.log(`2. Querying firewall rules for site: ${effectiveSiteId}...`);
    console.log('');

    // Try multiple endpoint paths for firewall rules
    const endpointPaths = [
      `/api/v2/sites/${effectiveSiteId}/firewall/rules`,
      `/sites/${effectiveSiteId}/firewall/rules`,
      `/api/firewall/rules?siteId=${effectiveSiteId}`,
      `/api/v2/firewall/rules?siteId=${effectiveSiteId}`,
    ];

    let rulesResponse;
    let rulesFound = false;
    
    for (const path of endpointPaths) {
      try {
        rulesResponse = await apiRequest('GET', path, null, token, cookies);
        
        // Check if we got a valid response
        if (rulesResponse.errorCode === 0 && Array.isArray(rulesResponse.result)) {
          rulesFound = true;
          break;
        } else if (Array.isArray(rulesResponse)) {
          rulesResponse = { errorCode: 0, result: rulesResponse };
          rulesFound = true;
          break;
        }
      } catch (e) {
        continue;
      }
    }

    if (!rulesFound) {
      console.error(`   ✗ Could not query firewall rules via API`);
      console.error('');
      console.error('Note: Firewall rules may need to be checked via Omada Controller web interface:');
      console.error(`  ${cloudControllerUrl}`);
      console.error('  Navigate to: Settings → Firewall → Firewall Rules');
      console.error('');
      console.error('Or firewall rules API may not be available for this controller type.');
      process.exit(1);
    }

    const rules = Array.isArray(rulesResponse.result) ? rulesResponse.result : [];
    console.log(`   ✓ Found ${rules.length} firewall rules`);
    console.log('');

    // Filter rules that might affect Blockscout
    const relevantRules = rules.filter((rule) => {
      const affectsBlockscoutIP = 
        !rule.dstIp || 
        rule.dstIp === BLOCKSCOUT_IP || 
        (typeof rule.dstIp === 'string' && rule.dstIp.includes('192.168.11')) ||
        rule.dstIp === '192.168.11.0/24';

      const affectsPort80 = 
        !rule.dstPort || 
        rule.dstPort === BLOCKSCOUT_PORT || 
        (typeof rule.dstPort === 'string' && rule.dstPort.includes(BLOCKSCOUT_PORT)) ||
        rule.dstPort === 'all' ||
        rule.dstPort === '0-65535';

      const isTCP = 
        !rule.protocol || 
        rule.protocol === 'tcp' || 
        rule.protocol === 'tcp/udp' || 
        rule.protocol === 'all';

      return rule.enable && (affectsBlockscoutIP || affectsPort80) && isTCP;
    });

    if (relevantRules.length > 0) {
      console.log('════════════════════════════════════════');
      console.log(`🔍 Found ${relevantRules.length} rule(s) that might affect Blockscout:`);
      console.log('════════════════════════════════════════');
      console.log('');

      relevantRules.forEach((rule, index) => {
        console.log(`Rule ${index + 1}: ${rule.name || rule.id || 'Unnamed'}`);
        console.log(`  ID: ${rule.id || 'N/A'}`);
        console.log(`  Enabled: ${rule.enable ? 'Yes ✓' : 'No ✗'}`);
        console.log(`  Action: ${rule.action || 'N/A'}`);
        console.log(`  Direction: ${rule.direction || 'N/A'}`);
        console.log(`  Protocol: ${rule.protocol || 'all'}`);
        console.log(`  Source IP: ${rule.srcIp || 'Any'}`);
        console.log(`  Source Port: ${rule.srcPort || 'Any'}`);
        console.log(`  Destination IP: ${rule.dstIp || 'Any'}`);
        console.log(`  Destination Port: ${rule.dstPort || 'Any'}`);
        console.log(`  Priority: ${rule.priority !== undefined ? rule.priority : 'N/A'}`);
        console.log('');

        if (rule.action === 'deny' || rule.action === 'reject') {
          console.log('  ⚠️  WARNING: This rule BLOCKS traffic!');
          console.log('');
        }
      });

      // Separate allow and deny rules
      const allowRules = relevantRules.filter((rule) => rule.action === 'allow');
      const denyRules = relevantRules.filter((rule) => rule.action === 'deny' || rule.action === 'reject');

      console.log('════════════════════════════════════════');
      console.log('Analysis');
      console.log('════════════════════════════════════════');
      console.log('');

      if (denyRules.length > 0 && allowRules.length === 0) {
        console.log('❌ Issue Found:');
        console.log('  Deny rules exist that block Blockscout, but no allow rules found.');
        console.log('  This explains the "No route to host" error.');
        console.log('');
        console.log('✅ Recommended Action:');
        console.log('  Create an allow rule in Omada Controller with HIGH priority:');
        console.log('');
        console.log('    Name: Allow Internal to Blockscout HTTP');
        console.log('    Enable: Yes');
        console.log('    Action: Allow');
        console.log('    Direction: Forward');
        console.log('    Protocol: TCP');
        console.log('    Source IP: 192.168.11.0/24 (or leave blank for Any)');
        console.log('    Destination IP: 192.168.11.140');
        console.log('    Destination Port: 80');
        console.log('    Priority: High (above deny rules)');
        console.log('');
      } else if (allowRules.length > 0 && denyRules.length > 0) {
        const highestAllowPriority = Math.max(...allowRules.map((r) => r.priority || 0));
        const lowestDenyPriority = Math.min(...denyRules.map((r) => r.priority || 9999));

        if (highestAllowPriority < lowestDenyPriority) {
          console.log('✅ Priority order looks correct (allow rules above deny rules).');
        } else {
          console.log('❌ Issue: Some deny rules have higher priority than allow rules.');
          console.log('  Adjust rule priority so allow rules are above deny rules.');
        }
        console.log('');
      } else if (allowRules.length > 0) {
        console.log('✅ Allow rules exist for Blockscout.');
        console.log('  If issues persist, check rule priority or default policies.');
        console.log('');
      }
    } else {
      console.log('════════════════════════════════════════');
      console.log('ℹ️  No firewall rules found that specifically target Blockscout.');
      console.log('════════════════════════════════════════');
      console.log('');

      // Check for deny rules
      const denyRules = rules.filter(
        (rule) => rule.enable && (rule.action === 'deny' || rule.action === 'reject')
      );

      if (denyRules.length > 0) {
        console.log(`⚠️  Found ${denyRules.length} deny/reject rules in total:`);
        console.log('');
        denyRules.slice(0, 10).forEach((rule) => {
          console.log(`  - ${rule.name || rule.id} (Priority: ${rule.priority || 'N/A'})`);
          if (rule.dstIp) console.log(`    Dest IP: ${rule.dstIp}`);
          if (rule.dstPort) console.log(`    Dest Port: ${rule.dstPort}`);
        });
        if (denyRules.length > 10) {
          console.log(`  ... and ${denyRules.length - 10} more`);
        }
        console.log('');
      }

      console.log('✅ Recommendation:');
      console.log('  Create an explicit allow rule to ensure Blockscout access.');
      console.log('');
    }

    // Show all rules summary
    console.log('════════════════════════════════════════');
    console.log('All Firewall Rules Summary');
    console.log('════════════════════════════════════════');
    console.log('');

    const enabledRules = rules.filter((r) => r.enable);
    const allowCount = enabledRules.filter((r) => r.action === 'allow').length;
    const denyCount = enabledRules.filter((r) => r.action === 'deny' || r.action === 'reject').length;

    console.log(`Total Rules: ${rules.length}`);
    console.log(`  Enabled: ${enabledRules.length}`);
    console.log(`  Allow Actions: ${allowCount}`);
    console.log(`  Deny/Reject Actions: ${denyCount}`);
    console.log('');

    console.log('════════════════════════════════════════');
    
  } catch (error) {
    console.error('\n❌ Error:');
    console.error('');
    if (error.message) {
      console.error(`  ${error.message}`);
    } else {
      console.error('  ', error);
    }
    if (error.stack) {
      console.error('');
      console.error('Stack trace:');
      console.error(error.stack);
    }
    process.exit(1);
  }
}

main();

