const express = require('express');
const axios = require('axios');
const path = require('path');
const https = require('https');
const rateLimit = require('express-rate-limit');

const app = express();
const PORT = 3333;

// Set up rate limiter: maximum of 100 requests per hour
const apiLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 100, // limit each IP to 100 requests per windowMs
  message: { error: 'Too many requests, please try again later.' }
});

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

// Track redirects function
async function trackRedirects(url, redirects = [], options = {}) {
  const startTime = Date.now();
  const currentRedirect = { 
    url, 
    timestamp: startTime,
    isSSL: url.toLowerCase().startsWith('https://') 
  };
  
  // Add the current URL to the redirects array
  if (redirects.length === 0) {
    redirects.push(currentRedirect);
  }
  
  try {
    // Prepare request config
    const config = {
      method: options.method || 'GET',
      url: url,
      maxRedirects: 0,
      validateStatus: status => status >= 200 && status < 600,
      timeout: 15000,
      responseType: 'text',
      decompress: true,
      headers: {}
    };
    
    // Add user agent if provided
    if (options.userAgent) {
      config.headers['User-Agent'] = options.userAgent;
    }
    
    // Add HTTPS agent for SSL info
    config.httpsAgent = new https.Agent({
      rejectUnauthorized: false, // Allow self-signed certs for testing
      checkServerIdentity: (host, cert) => {
        // Capture certificate info
        return undefined; // Allow connection
      }
    });
    
    // Make the request
    const response = await axios(config);
    
    // Calculate the duration for this request
    const endTime = Date.now();
    
    // Get response metadata
    const metadata = {
      status: response.status,
      statusText: response.statusText,
      headers: response.headers,
      contentType: response.headers['content-type'],
      contentLength: response.headers['content-length'],
      server: response.headers['server'],
      date: response.headers['date'],
      protocol: response.request.protocol,
      method: config.method
    };
    
    // Get SSL certificate info if HTTPS
    let sslInfo = null;
    if (url.toLowerCase().startsWith('https://')) {
      try {
        const urlObj = new URL(url);
        const socket = response.request.socket;
        
        if (socket && socket.getPeerCertificate) {
          const cert = socket.getPeerCertificate(true);
          sslInfo = {
            valid: socket.authorized,
            issuer: cert.issuer,
            subject: cert.subject,
            validFrom: cert.valid_from,
            validTo: cert.valid_to,
            fingerprint: cert.fingerprint
          };
        }
      } catch (error) {
        sslInfo = { error: 'Failed to retrieve SSL info' };
      }
    }
    
    // Get response body (truncate if too large)
    let responseBody = response.data;
    if (typeof responseBody === 'string' && responseBody.length > 5000) {
      responseBody = responseBody.substring(0, 5000) + '... [truncated]';
    }
    
    // Update the current redirect with all the detailed info
    if (redirects.length > 0 && redirects[redirects.length - 1].url === url) {
      redirects[redirects.length - 1].duration = endTime - startTime;
      redirects[redirects.length - 1].statusCode = response.status;
      redirects[redirects.length - 1].statusText = response.statusText;
      redirects[redirects.length - 1].metadata = metadata;
      redirects[redirects.length - 1].responseBody = responseBody;
      redirects[redirects.length - 1].sslInfo = sslInfo;
    } else {
      currentRedirect.duration = endTime - startTime;
      currentRedirect.statusCode = response.status;
      currentRedirect.statusText = response.statusText;
      currentRedirect.metadata = metadata;
      currentRedirect.responseBody = responseBody;
      currentRedirect.sslInfo = sslInfo;
    }
    
    // Check if we have a redirect
    if (response.status >= 300 && response.status < 400 && response.headers.location) {
      // Get the next URL
      let nextUrl = response.headers.location;
      
      // Handle relative URLs
      if (!nextUrl.startsWith('http')) {
        const baseUrl = new URL(url);
        nextUrl = new URL(nextUrl, baseUrl.origin).href;
      }
      
      // Create the next redirect object
      const nextRedirect = { 
        url: nextUrl, 
        timestamp: endTime,
        isSSL: nextUrl.toLowerCase().startsWith('https://') 
      };
      
      // Add to redirects array
      redirects.push(nextRedirect);
      
      // Continue following redirects (but always use GET for subsequent requests per HTTP spec)
      const nextOptions = { ...options, method: 'GET' };
      return await trackRedirects(nextUrl, redirects, nextOptions);
    } else {
      // This is the final page
      if (redirects.length > 0 && redirects[redirects.length - 1].url === url) {
        redirects[redirects.length - 1].final = true;
      } else {
        currentRedirect.final = true;
        redirects.push(currentRedirect);
      }
      return redirects;
    }
  } catch (error) {
    // Handle errors
    const endTime = Date.now();
    
    // Update the current redirect with the duration and error info
    if (redirects.length > 0 && redirects[redirects.length - 1].url === url) {
      redirects[redirects.length - 1].duration = endTime - startTime;
      redirects[redirects.length - 1].error = error.message;
      redirects[redirects.length - 1].final = true;
      
      // Try to get response info from error object if available
      if (error.response) {
        redirects[redirects.length - 1].statusCode = error.response.status;
        redirects[redirects.length - 1].statusText = error.response.statusText;
        redirects[redirects.length - 1].metadata = {
          status: error.response.status,
          statusText: error.response.statusText,
          headers: error.response.headers,
          method: options.method
        };
      }
    } else {
      currentRedirect.duration = endTime - startTime;
      currentRedirect.error = error.message;
      currentRedirect.final = true;
      
      // Try to get response info from error object if available
      if (error.response) {
        currentRedirect.statusCode = error.response.status;
        currentRedirect.statusText = error.response.statusText;
        currentRedirect.metadata = {
          status: error.response.status,
          statusText: error.response.statusText,
          headers: error.response.headers,
          method: options.method
        };
      }
      
      redirects.push(currentRedirect);
    }
    
    return redirects;
  }
}

// Original endpoint (now deprecated but maintained for backward compatibility)
app.post('/api/track', async (req, res) => {
  const { url, method = 'GET', userAgent } = req.body;
  
  if (!url) {
    return res.status(400).json({ error: 'URL is required' });
  }
  
  try {
    // Ensure URL has a protocol
    let inputUrl = url;
    if (!inputUrl.startsWith('http://') && !inputUrl.startsWith('https://')) {
      inputUrl = 'http://' + inputUrl;
    }
    
    // Set up request options
    const options = {
      method: method.toUpperCase(),
      userAgent
    };
    
    const redirectChain = await trackRedirects(inputUrl, [], options);
    res.json({ redirects: redirectChain });
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: 'Failed to track redirects' });
  }
});

// New versioned API endpoints with rate limiting
// API v1 track endpoint
app.post('/api/v1/track', apiLimiter, async (req, res) => {
  const { url, method = 'GET', userAgent } = req.body;
  
  if (!url) {
    return res.status(400).json({ 
      error: 'URL is required',
      status: 400,
      success: false
    });
  }
  
  try {
    // Ensure URL has a protocol
    let inputUrl = url;
    if (!inputUrl.startsWith('http://') && !inputUrl.startsWith('https://')) {
      inputUrl = 'http://' + inputUrl;
    }
    
    // Set up request options
    const options = {
      method: method.toUpperCase(),
      userAgent
    };
    
    const redirectChain = await trackRedirects(inputUrl, [], options);
    
    // Format the response in a more standardized API format
    res.json({
      success: true,
      status: 200,
      data: {
        url: inputUrl,
        method: options.method,
        redirectCount: redirectChain.length - 1,
        finalUrl: redirectChain[redirectChain.length - 1].url,
        finalStatusCode: redirectChain[redirectChain.length - 1].statusCode,
        redirects: redirectChain
      }
    });
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ 
      error: 'Failed to track redirects',
      message: error.message,
      status: 500,
      success: false
    });
  }
});

// API v1 track endpoint with GET method support (for easy browser/curl usage)
app.get('/api/v1/track', apiLimiter, async (req, res) => {
  const { url, method = 'GET', userAgent } = req.query;
  
  if (!url) {
    return res.status(400).json({ 
      error: 'URL parameter is required',
      status: 400,
      success: false
    });
  }
  
  try {
    // Ensure URL has a protocol
    let inputUrl = url;
    if (!inputUrl.startsWith('http://') && !inputUrl.startsWith('https://')) {
      inputUrl = 'http://' + inputUrl;
    }
    
    // Set up request options
    const options = {
      method: (method || 'GET').toUpperCase(),
      userAgent
    };
    
    const redirectChain = await trackRedirects(inputUrl, [], options);
    
    // Format the response in a more standardized API format
    res.json({
      success: true,
      status: 200,
      data: {
        url: inputUrl,
        method: options.method,
        redirectCount: redirectChain.length - 1,
        finalUrl: redirectChain[redirectChain.length - 1].url,
        finalStatusCode: redirectChain[redirectChain.length - 1].statusCode,
        redirects: redirectChain
      }
    });
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ 
      error: 'Failed to track redirects',
      message: error.message,
      status: 500,
      success: false
    });
  }
});

// API documentation endpoint
app.get('/api/docs', (req, res) => {
  const apiDocs = `
<!DOCTYPE html>
<html>
<head>
  <title>URL Redirect Tracker API</title>
  <style>
    body { font-family: Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }
    pre { background: #f4f4f4; padding: 10px; border-radius: 5px; overflow-x: auto; }
    table { border-collapse: collapse; width: 100%; }
    th, td { text-align: left; padding: 8px; border: 1px solid #ddd; }
    th { background-color: #f2f2f2; }
    .method { display: inline-block; padding: 3px 8px; border-radius: 3px; color: white; font-weight: bold; }
    .get { background-color: #61affe; }
    .post { background-color: #49cc90; }
  </style>
</head>
<body>
  <h1>URL Redirect Tracker API Documentation</h1>
  <p>This API allows you to programmatically track and analyze URL redirect chains with detailed information.</p>
  
  <h2>Rate Limiting</h2>
  <p>The API is limited to 100 requests per hour per IP address.</p>
  
  <h2>Endpoints</h2>
  
  <h3><span class="method post">POST</span> /api/v1/track</h3>
  <p>Track a URL and get the full redirect chain using a POST request.</p>
  
  <h4>Request Parameters (JSON Body)</h4>
  <table>
    <tr>
      <th>Parameter</th>
      <th>Type</th>
      <th>Required</th>
      <th>Description</th>
    </tr>
    <tr>
      <td>url</td>
      <td>string</td>
      <td>Yes</td>
      <td>The URL to track (e.g., "example.com")</td>
    </tr>
    <tr>
      <td>method</td>
      <td>string</td>
      <td>No</td>
      <td>HTTP method (GET, HEAD, POST). Default: "GET"</td>
    </tr>
    <tr>
      <td>userAgent</td>
      <td>string</td>
      <td>No</td>
      <td>Custom User-Agent header</td>
    </tr>
  </table>
  
  <h4>Example Request</h4>
  <pre>
curl -X POST http://localhost:${PORT}/api/v1/track \\
  -H "Content-Type: application/json" \\
  -d '{
    "url": "github.com",
    "method": "GET"
  }'
  </pre>
  
  <h3><span class="method get">GET</span> /api/v1/track</h3>
  <p>Track a URL and get the full redirect chain using a GET request with query parameters.</p>
  
  <h4>Request Parameters (Query String)</h4>
  <table>
    <tr>
      <th>Parameter</th>
      <th>Type</th>
      <th>Required</th>
      <th>Description</th>
    </tr>
    <tr>
      <td>url</td>
      <td>string</td>
      <td>Yes</td>
      <td>The URL to track (e.g., "example.com")</td>
    </tr>
    <tr>
      <td>method</td>
      <td>string</td>
      <td>No</td>
      <td>HTTP method (GET, HEAD, POST). Default: "GET"</td>
    </tr>
    <tr>
      <td>userAgent</td>
      <td>string</td>
      <td>No</td>
      <td>Custom User-Agent header</td>
    </tr>
  </table>
  
  <h4>Example Request</h4>
  <pre>
curl "http://localhost:${PORT}/api/v1/track?url=github.com&method=GET"
  </pre>
  
  <h2>Response Format</h2>
  <pre>
{
  "success": true,
  "status": 200,
  "data": {
    "url": "http://github.com",
    "method": "GET",
    "redirectCount": 1,
    "finalUrl": "https://github.com/",
    "finalStatusCode": 200,
    "redirects": [
      // Array of redirect objects with detailed information
    ]
  }
}
  </pre>
  
  <p><a href="/">Back to URL Redirect Tracker</a></p>
</body>
</html>
  `;
  
  res.send(apiDocs);
});

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
}); 