import express, { Application, Request, Response, NextFunction }  from 'express';
import { createProxyServer } from 'http-proxy';
import cookieParser from 'cookie-parser';
import fs, { write } from 'fs';
import path from 'path';
import cheerio from 'cheerio';
// import bodyParser from 'body-parser';
import cors from 'cors';
import { getBaseUrl, getUrlPath, simpleRewriteLinks } from './helpers/helpers';
import { proxyReqHandler, proxyResHandler } from './includes/throughProxy';
import { directReqHandler, directResHandler } from './includes/directProcessing';
// import { directReqHandler, directResHandler } from './includes/directProcessingBasic';
import corsHandler from './cors';
import { proxyThroughPuppeteer } from './includes/puppeteerProcessing';
import { getPublicHost } from './helpers/getPublicHost';
import https, { request as httpsRequest } from 'https';
import { writeToLog } from './helpers/writeToLog';
import { ServerResponse } from 'http';
import stream from 'stream';
import { Readable } from 'stream';
import getRawBody from 'raw-body';

let originalUrl: string | undefined  = undefined;
let isProxy = false;

const app = express();
// Use cookie-parser middleware
// app.use((req: Request, res: Response, next: NextFunction) => {
//   cookieParser()(req, res, next);
// });
app.use(cookieParser());
app.use(cors());

const proxy = createProxyServer({
  target: 'http://www.booking.com', // Replace with your target URL
  changeOrigin: true,
  // secure: true,
  selfHandleResponse: true, // Required for modifying the response
  // selfHandleResponse: false,
  preserveHeaderKeyCase: true,
  // followRedirects: true,
  agent: new https.Agent({
    rejectUnauthorized: false, // TEMPORARY: For debugging only
  }),
});


// const baseUrl = 'http://localhost:3005/proxy?url='; // replace wtih env vars for different envs
// const baseUrl = 'http://localhost:3005/proxy/'; // replace wtih env vars for different envs
const baseUrl = getPublicHost();


proxy.on('proxyReq', (proxyReq, req) => {
  if (isProxy) {
    proxyReqHandler(req, proxyReq); // Use the specific handler for proxy
  } else {
    // You can add different logic for non-proxy requests if necessary
    writeToLog(`>> proxyReqHandler called on url: ${req.url}`);
    const host = proxyReq.getHeader('host');
    const path = req.url;
    console.log(`🚀 Proxying to: http://${host}${path}`);
    directReqHandler(req, proxyReq); // Use the specific handler for proxy
  }
});

proxy.on('proxyRes', (proxyRes, req, res) => {
  if (isProxy) {
    if (originalUrl) {
      proxyResHandler(proxyRes, req, res, originalUrl); // Use the specific handler for proxy
    }
  } else {
    writeToLog(`>> proxyResHandler called  on url: ${req.url}`);
    // You can add different logic for non-proxy responses if necessary
    if (originalUrl) {
      directResHandler(proxyRes, req, res, originalUrl); // Use the specific handler for proxy
    } else {
      (res as Response).status(500).send(`Original URL not available! Target URL: ${req.url}, Location: ${proxyRes.headers['location']}`);
    }
  }
  
});


app.get('/proxyUrl', (req, res) => {
  corsHandler(req, res);
});



app.get('/proxy/:url(*)', (req: any, res: any) => {
  isProxy = true;
  const oU = decodeURIComponent((req as any).params[0]); // This captures everything after '/proxy/'
  console.log('>>req', (req as any).params[0]);
  console.log('toU:', oU)
  const ooU = req.query.url;
  console.log('> originalUrl:', ooU)
  if (!oU || typeof oU !== 'string') {
    return res.status(400).send('Please provide a valid URL as a string');
  }
  originalUrl = oU;

  req.url = decodeURIComponent(req.url.replace(/^\/proxy/, '/search'));
  console.log('req.url:', req.url)

  // console.log('proxy:', proxy)
  proxy.web(req, res, { target: originalUrl });
});

const pageLoadLogs = path.join(__dirname, 'pageload.log'); // Specify the path of the file

app.get('/browse/:url(*)', (req: any, res: any) => {
  isProxy = false;
  console.log('Full URL:', req.url); // Log the complete URL request
  console.log('Request Params:', req.params); // Log the parameters captured
  console.log('req param', (req as any).params[0]);

  const originalUrl = decodeURIComponent((req as any).params[0]); // This captures everything after '/browse/'
  if (!originalUrl || typeof originalUrl !== 'string') {
    return res.status(400).send('Please provide a valid URL as a string');
  }
  const baseUrl = getBaseUrl(originalUrl);
  // const urlPath = originalUrl.replace(baseUrl, '');
  const urlPath = getUrlPath(originalUrl);
  const queryString = Object.keys((req as any).query)
  .map(key => `${encodeURIComponent(key)}=${encodeURIComponent((req as any).query[key])}`)
  .join('&');
  // const redirPath =`http://localhost:3005${urlPath}${queryString ? `?${queryString}` : ''}`;
  const redirPath =`${getPublicHost('basePath')}${urlPath}${queryString ? `?${queryString}` : ''}`;
  console.log('>>> BASE URL:', baseUrl);
  console.log('>>> url Path:', urlPath);
  console.log('>>> REDIR Path:', redirPath);
  // console.log('>>req', (req as any).params[0]);
  // console.log('>> query:', (req as any).query);
  // console.log('>> queryString:', queryString);

  writeToLog(`${new Date().toISOString()} - BASE URL: ${baseUrl} URL PATH: ${urlPath} - REDIR PATH: ${redirPath}`);

  res.cookie('targetUrl', baseUrl, { 
      maxAge: 24*60*60*1000, // Cookie expiration time in milliseconds
      httpOnly: true, // The cookie is inaccessible to JavaScript's Document.cookie API
      secure: process.env.NODE_ENV === 'production', // Set to true if using HTTPS
      // sameSite: 'Strict', // Controls whether cookies are sent with cross-site requests
  });
  // res.send('Cookie has been set!');
  // Redirect to another route, just add the target URL if any
  res.redirect(redirPath);
});



// app.get('/*', (req: any, res: any) => {
//   const originUrl = req.cookies.targetUrl; // Access the cookie
//   // const originUrl = 'https://www.booking.com'; // Access the cookie
//   if (!originUrl || typeof originUrl !== 'string') {
//     return res.status(400).send('Please provide a valid URL as a string');
//   }
//   const urlPath = req.url.replace(getPublicHost('basePath'), '');
//   const queryString = Object.keys((req as any).query)
//   .map(key => `${encodeURIComponent(key)}=${encodeURIComponent((req as any).query[key])}`)
//   .join('&');
//   const parsedQueryString = queryString ? `?${queryString.replace('uri=', `uri=${encodeURIComponent(queryString)}`)}` : '';
//   const targetUrl = originalUrl = `${originUrl}${urlPath}${parsedQueryString ? `?${queryString}` : ''}`;
//   // const targetUrl = originalUrl = "https://www.booking.com";

//   console.log('-- break line --');
  
//   console.log('>>> TARGET Path:', targetUrl);
//   console.log('>>req', (req as any).params[0]);
//   console.log('>> query:', (req as any).query);
//   console.log('>> queryString:', queryString);
//   if (targetUrl.includes('favicon.ico')) {
//     return res.status(404).send('skipping favicon');
//   }

//   const dataToAppend = `${new Date().toISOString()} - TARGET PATH: ${targetUrl}\n`;

//   fs.appendFile(pageLoadLogs, dataToAppend + '\n', (err) => {
//     if (err) {
//       console.error('Error appending to file:', err);
//       return res.status(500).send('Failed to append data to file.');
//     }
//   });

//   // proxy.web(req, res, { target: targetUrl });
//   proxy.web(req, res, 
//     { 
//       target: targetUrl, 
//       changeOrigin: true,            // make Host header match booking.com
//       secure: true,                  // trust HTTPS
//       followRedirects: false         // we'll rewrite Location ourselves
//     }, 
//     (error) => {
//       console.error('Proxy error:', error);
//       res.status(500).send('Something went wrong');
//   });

//   // proxyThroughPuppeteer(req, res, targetUrl);
// });

app.get('orig/*', (req: any, res: any) => {
  const originUrl = req.cookies.targetUrl; // Access the cookie
  // const originUrl = 'https://www.booking.com'; // Access the cookie
  if (!originUrl || typeof originUrl !== 'string') {
    return res.status(400).send('Please provide a valid URL as a string');
  }
  const urlPath = req.url.replace(getPublicHost('basePath'), '');

  const cleanedPath = req.url.replace(getPublicHost('basePath'), '');
  const parsedIncomingUrl = new URL(`http://dummy.com${cleanedPath}`); // dummy base to parse pathname + search
  const path = parsedIncomingUrl.pathname;
  const incomingParams = parsedIncomingUrl.searchParams;

  // Get query params from req.query (Express parses them)
  const mergedParams = new URLSearchParams();

  // Add existing query params from req.query (e.g., from original user input)
  Object.entries(req.query).forEach(([key, value]) => {
    if (Array.isArray(value)) {
      value.forEach(v => mergedParams.append(key, v));
    } else {
      mergedParams.set(key, value as string);
    }
  });

  // // Avoid duplicates: don't re-add keys that already exist
  // for (const [key, value] of incomingParams.entries()) {
  //   if (!mergedParams.has(key)) {
  //     mergedParams.append(key, value);
  //   }
  // }

  // Compose final target URL
  const queryString = mergedParams.toString();
  writeToLog(`${new Date().toISOString()} - QUERY STRING: ${queryString}\n`);
  const targetUrl = originalUrl = `${originUrl}${path}${queryString ? `?${queryString}` : ''}`;
  writeToLog(`${new Date().toISOString()} - targetUrl after applying: ${targetUrl}\n`);

  console.log('-- break line --');
  
  console.log('>>> TARGET Path:', targetUrl);
  console.log('>>req', (req as any).params[0]);
  console.log('>> query:', (req as any).query);
  console.log('>> queryString:', queryString);
  if (targetUrl.includes('favicon.ico')) {
    return res.status(404).send('skipping favicon');
  }

  writeToLog(`${new Date().toISOString()} - TARGET PATH: ${targetUrl}\n`);
  
  proxy.web(req, res, 
    { 
      target: targetUrl, 
      changeOrigin: true,            // make Host header match booking.com
      secure: true,                       // trust HTTPS
      followRedirects: false         // we'll rewrite Location ourselves
    }, 
    (error) => {
      console.error('Proxy error:', error);
      res.status(500).send('Something went wrong');
  });

});

// app.use((req, res, next) => {
//   if (req.url.includes('/otp/is-enabled')) {
//     writeToLog(`🔍 req,method: ${req.method}`);
//     writeToLog(`🔍 OTP POST body: ${req.body}`);
//   }
//   next();
// });

app.use((req: any, res, next) => {
  req._reqId = `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
  writeToLog(`[INIT] New request: ${req.method} ${req.url} ID: ${req._reqId}`);
  next();
});

// Middleware to collect the raw body before proxy
// app.use((req, res, next) => {
//   if (req.method === 'POST' || req.method === 'PUT') {
//     let bodyData = '';
//     req.on('data', (chunk) => {
//       bodyData += chunk;
//     });
//     req.on('end', () => {
//       (req as any).rawBody = bodyData;
//       writeToLog(`🔍 Req url: ${req.url}`);
//       writeToLog(`🔍 POST body: ${bodyData}`);
//       next();
//     });
//   } else {
//     next();
//   }
// });

// app.use((req, res, next) => {
//   // Only parse POST/PUT/PATCH with appropriate content-type
//   if (
//     ['POST', 'PUT', 'PATCH'].includes(req.method) &&
//     req.headers['content-type']?.includes('application/json')
//   ) {
//     getRawBody(req)
//       .then((buf) => {
//         (req as any).rawBody = buf;
//         next();
//       })
//       .catch((err) => {
//         console.error('Error parsing raw body:', err);
//         res.status(400).send('Invalid request body');
//       });
//   } else {
//     next();
//   }
// });

// Extend Express.Request to include custom fields
interface ExtendedRequest extends Request {
  rawBody?: Buffer;
  bodyStream?: Readable;
}

// Middleware to parse the raw body
// app.use((req: ExtendedRequest, res: Response, next: NextFunction) => {
//   if (
//     ['POST', 'PUT', 'PATCH'].includes(req.method) &&
//     req.headers['content-type']?.includes('application/json')
//   ) {
//     getRawBody(req)
//       .then((rawBody) => {
//         req.rawBody = rawBody;
//         req.bodyStream = rawBodyToStream(rawBody);
//         writeToLog(`[rawBody set for ${req.url}] ${rawBody.toString()}`);
//         next();
//       })
//       .catch((err) => {
//         console.error('Error parsing raw body:', err);
//         res.status(400).send('Invalid body');
//       });
//   } else {
//     next();
//   }
// });

// @ts-ignore: Unreachable code error
app.use(async (req: any, res, next) => {
  if (
    ['POST', 'PUT', 'PATCH'].includes(req.method) &&
    req.headers['content-type']?.includes('application/json')
  ) {
    try {
      if (!req.rawBody) {
        const rawBody = await getRawBody(req);
        req.rawBody = rawBody;
        writeToLog(`[RAW BODY - ${req.url}], ${rawBody.toString()}`);
      }
      next();
    } catch (err) {
      console.error('Error parsing raw body:', err);
      return res.status(400).send('Invalid body');
    }
  } else {
    next();
  }
});


function rawBodyToStream(rawBody: string | Buffer): Readable {
  const stream = new Readable({
    read() {
      this.push(rawBody);
      this.push(null);
    }
  });

  return stream;
}


app.all('/*', (req: any, res: any) => {
  const originUrl = req.cookies.targetUrl;

  if (!originUrl || typeof originUrl !== 'string') {
    return res.status(400).send('Please provide a valid URL as a string');
  }

  const cleanedPath = req.url.replace(getPublicHost('basePath'), '');
  const parsedUrl = new URL(`http://dummy.com${cleanedPath}`);
  const path = parsedUrl.pathname;
  const originalQueryParams = parsedUrl.searchParams;

  // Merge query params: start with original query in path
  const mergedParams = new URLSearchParams();

  for (const [key, value] of originalQueryParams.entries()) {
    if (!mergedParams.has(key)) {
      mergedParams.append(key, value);
    }
  }

  // Then merge in req.query (may have duplicates, avoid re-adding)
  Object.entries(req.query).forEach(([key, value]) => {
    if (!mergedParams.has(key)) {
      if (Array.isArray(value)) {
        mergedParams.append(key, value[0]); // only first item if array
      } else {
        mergedParams.append(key, value as string);
      }
    }
  });

  const queryString = mergedParams.toString();
  const targetUrl = originalUrl = `${originUrl}${path}${queryString ? `?${queryString}` : ''}`;
  // const targetUrl = `${originUrl}${path}${queryString ? `?${queryString}` : ''}`;

  writeToLog(`${new Date().toISOString()} - QUERY STRING: ${queryString}\n`);
  writeToLog(`${new Date().toISOString()} - TARGET PATH: ${targetUrl}\n`);

  console.log('-- break line --');
  console.log('>>> TARGET Path:', targetUrl);
  console.log('>> req.query:', req.query);
  console.log('>> queryString:', queryString);

  if (targetUrl.includes('favicon.ico')) {
    return res.status(404).send('skipping favicon');
  }

  // res.on('finish', () => console.log('✅ Response sent'));
  // res.on('close', () => console.log('❌ Response connection closed early'));

  // writeToLog(`buffer: ${ JSON.stringify(rawBodyToStream((req as any).rawBody))}\n`);
  // const rawBody = (req as any).rawBody;

  // if (rawBody) {
  //   const newStream = rawBodyToStream(rawBody);

  //   // Mimic original request
  //   // @ts-ignore
  //   newStream.headers = {
  //     ...req.headers,
  //     'origin': 'https://www.booking.com',
  //     'content-length': Buffer.byteLength(rawBody).toString(),
  //     'content-type': 'application/json' // just in case
  //   };
  //   // @ts-ignore
  //   newStream.method = req.method;
  //   // @ts-ignore
  //   newStream.url = req.url;
  //   // @ts-ignore: override types to match Request interface
  //   req = newStream;
  // }

  res.setHeader('Connection', 'close');
  writeToLog(`[DEBUG] Raw body for ${req.url}: ${req.rawBody?.toString()}`);

  proxy.web(
    req,
    res,
    {
      target: targetUrl,
      changeOrigin: true,
      secure: true,
      followRedirects: false,
      ignorePath: true,
      // // @ts-ignore
      // buffer: {
      //   pipe: (output) => {
      //     output.end(req.rawBody);
      //     return output;
      //   }
      // }
      // buffer: rawBodyToStream((req as any).rawBody)
    },
    (error) => {
      if (!res.headersSent) {
        console.error('Proxy error:', error);
        res.status(500).send('Something went wrong');
      } else {
        console.warn('Headers already sent; cannot respond to error.');
      }
    }
  );
});

// // Serve frontend static files
// const distDir = path.join(__dirname, '../frontend/build'); // Adjust this if your frontend build output is different
// app.use(express.static(distDir));

// app.get('*', (req, res) => {
//   res.sendFile(path.join(__dirname, '../../frontend/build', 'index.html')); // Serve the frontend
// });

const port = process.env.PORT || 3005;

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});