import * as fs from 'fs';
import type { IncomingMessage, ServerResponse } from 'http';
import * as path from 'path';
import { URL } from 'url';

import { getShowcaseData } from './preview';
import { fetchTiles, updateTilesSection, updateCustomContent } from './utils';


/**
 * Extract query parameter from URL
 */
function getQueryParam(url: string, paramName: string): string | null {
  try {
    const urlObj = new URL(url, 'http://localhost');
    return urlObj.searchParams.get(paramName);
  } catch {
    return null;
  }
}

/**
 * Read request body as JSON
 */
function readRequestBody(req: IncomingMessage): Promise<Record<string, unknown>> {
  return new Promise((resolve, reject) => {
    let body = '';
    req.on('data', (chunk) => {
      body += chunk.toString();
    });
    req.on('end', () => {
      try {
        const parsed = body ? JSON.parse(body) : null;
        resolve(parsed);
      } catch (error) {
        reject(error);
      }
    });
    req.on('error', (error) => {
      reject(error);
    });
  });
}

/**
 * Set no-cache headers to prevent browser caching
 */
function setNoCacheHeaders(res: ServerResponse): void {
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
  res.setHeader('Pragma', 'no-cache');
  res.setHeader('Expires', '0');
  res.setHeader('Surrogate-Control', 'no-store');
}

/**
 * Get all available showcases for a section from the dist folder
 */
function getAvailableShowcases(sectionName: string, distPath: string): string[] {
  try {
    const showcasesPath = path.join(distPath, 'sections', sectionName, 'js', 'showcases');

    if (!fs.existsSync(showcasesPath)) {
      console.warn(`[API Routes] Showcases directory not found for section "${sectionName}": ${showcasesPath}`);
      return [];
    }

    const showcases = fs.readdirSync(showcasesPath, { withFileTypes: true })
      .filter(dirent => dirent.isFile() && dirent.name.endsWith('.mjs'))
      .map(dirent => dirent.name.replace('.mjs', ''))
      .sort((a, b) => {
        // Sort numerically if both are numbers, otherwise alphabetically
        const aNum = parseInt(a, 10);
        const bNum = parseInt(b, 10);
        if (!isNaN(aNum) && !isNaN(bNum)) {
          return aNum - bNum;
        }
        return a.localeCompare(b);
      });

    return showcases;
  } catch (error) {
    console.error(`[API Routes] Error reading showcases for section "${sectionName}":`, error);
    return [];
  }
}

/**
 * Get all showcase IDs for a given section
 */
function getShowcaseIds(sectionName: string, distPath: string): string[] {
  return getAvailableShowcases(sectionName, distPath);
}

/**
 * Handle GET /api/v1/tile
 * Returns tile data with content and design for all showcases in multiple sections
 * Fetches tiles from remote once and updates them for each section/showcase
 * Accepts section names as parameter from Chrome extension
 */
async function handleGetTile(
  _req: IncomingMessage,
  res: ServerResponse,
  sectionNames: string[],
  authToken?: string,
  tileUrl?: string,
): Promise<void> {
  try {
    const distPath = path.resolve(process.cwd(), 'dist');

    // Fetch tiles from remote once
    let responseData = await fetchTiles(authToken, tileUrl);

    // Fetch all showcase data in parallel
    const showcaseDataPromises = sectionNames.flatMap((sectionName) => {
      const showcaseIds = getShowcaseIds(sectionName, distPath);
      return showcaseIds.map(async (showcaseId) => {
        const { content, design } = await getShowcaseData(
          sectionName,
          showcaseId,
          distPath,
        );
        return { sectionName, showcaseId, content, design };
      });
    });

    const allShowcaseData = await Promise.all(showcaseDataPromises);

    for (const { sectionName, showcaseId, content, design } of allShowcaseData) {
      responseData = updateTilesSection(responseData, sectionName, showcaseId, content, design);
    }

    res.statusCode = 200;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify(responseData, null, 2));
  } catch (error) {
    console.error('[API Routes] ❌ Error processing tile data:', error);
    res.statusCode = 500;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify({
      error: 'Failed to process tile data',
      message: error instanceof Error ? error.message : String(error),
    }));
  }
}

/**
 * Handle PUT /api/v1/tile
 * Updates tiles with content and design from remote
 * Accepts an array of tiles in the request body
 * For each tile, fetches remote data, updates content and design, and sends to original_url
 */
async function handleUpdateTile(
  req: IncomingMessage,
  res: ServerResponse,
  authToken?: string,
  originalUrl?: string,
): Promise<void> {
  const requestBody = await readRequestBody(req);
  try {
    // Read request body containing tiles array

    if (!requestBody || !Array.isArray(requestBody.tiles)) {
      res.statusCode = 400;
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify({
        error: 'Invalid request body',
        message: 'Expected { tiles: [...] } in request body',
      }));
      return;
    }

    const tiles = requestBody.tiles;

    // Fetch tiles from remote - add published=false query parameter
    const tilesUrl = originalUrl ? `${originalUrl}?published=false` : '';
    const remoteTiles = await fetchTiles(authToken, tilesUrl);

    if (!remoteTiles || !remoteTiles.tiles) {
      console.error('Can\'t fetch remote tiles or they dont exist from url', tilesUrl);
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify(requestBody, null, 2));
      return;
    }

    // Iterate over tiles from request body and update with remote data
    const updatedTiles = tiles.map((tile: Record<string, unknown>) => {
      // Find matching tile in remote data by id
      const remoteTile = remoteTiles.tiles.find((rt: Record<string, unknown>) => rt.id === tile.id);

      if (remoteTile) {
        // Update content and design from remote
        return {
          ...tile,
          content: remoteTile.content,
          design: remoteTile.design,
        };
      }

      // If no match found, return tile as-is
      return tile;
    });

    // Prepare updated request body
    const updatedRequestBody = {
      ...requestBody,
      tiles: updatedTiles,
    };

    // Make PUT request to original_url with updated tiles
    if (!originalUrl) {
      console.error('Tile update url was not provided');
      res.statusCode = 400;
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify(requestBody, null, 2));
      return;
    }

    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
    };

    if (authToken) {
      headers['Authorization'] = authToken;
    }

    const response = await fetch(originalUrl, {
      method: 'PUT',
      headers,
      body: JSON.stringify(updatedRequestBody),
    });

    if (!response.ok) {
      console.error('[API Routes] Failed to update tiles at original_url:', response.status, response.statusText);
      res.statusCode = response.status;
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify(requestBody, null, 2));
      return;
    }

    const responseData = await response.json();

    res.statusCode = 200;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify(responseData, null, 2));
  } catch (error) {
    console.error('[API Routes] ❌ Error updating tiles:', error);
    res.statusCode = 500;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify(requestBody, null, 2));
  }
}

/**
 * Handle /api/v1/tile (GET and PUT)
 * Routes to appropriate handler based on request method
 */
export async function handleTile(
  req: IncomingMessage,
  res: ServerResponse,
  sectionNames: string[],
  authToken?: string,
  originalUrl?: string,
): Promise<void> {
  const method = req.method?.toUpperCase();

  if (method === 'GET') {
    await handleGetTile(req, res, sectionNames, authToken, originalUrl);
  } else if (method === 'PUT') {
    await handleUpdateTile(req, res, authToken, originalUrl);
  } else {
    res.statusCode = 405;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify({
      error: 'Method not allowed',
      allowedMethods: ['GET', 'PUT'],
    }));
  }
}

/**
 * Handle GET /api/v1/sections
 * Returns list of available sections with their showcases
 */
export function handleGetSections(
  _req: IncomingMessage,
  res: ServerResponse,
): void {
  try {
    const distPath = path.resolve(process.cwd(), 'dist');
    const sectionsPath = path.join(distPath, 'sections');

    // Check if sections directory exists
    if (!fs.existsSync(sectionsPath)) {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify({ sections: [] }));
      return;
    }

    // Read all directories in the sections folder
    const sectionNames = fs.readdirSync(sectionsPath, { withFileTypes: true })
      .filter(dirent => dirent.isDirectory())
      .map(dirent => dirent.name);

    // Build sections array with showcase data
    const sections = sectionNames.map(sectionName => ({
      name: sectionName,
      showcases: getAvailableShowcases(sectionName, distPath),
    }));

    res.statusCode = 200;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify({ sections }, null, 2));
  } catch (error) {
    console.error('[API Routes] ❌ Error listing sections:', error);
    res.statusCode = 500;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify({
      error: 'Failed to list sections',
      message: error instanceof Error ? error.message : String(error),
    }));
  }
}

/**
 * Handle POST /api/v1/custom_content
 * Updates custom content for multiple sections
 * Accepts section names as parameter from Chrome extension
 */
export async function handleUpdateCustomContent(
  _req: IncomingMessage,
  res: ServerResponse,
  sectionNames: string[],
  authToken?: string,
  customContentUrl?: string,
): Promise<void> {
  try {
    const distPath = path.resolve(process.cwd(), 'dist');
    const tilesUrl = customContentUrl ? customContentUrl.replace('/custom_content', '/tile?published=false') : '';

    // Update custom content for all sections
    const updatedCustomContent = await updateCustomContent(
      sectionNames,
      distPath,
      authToken,
      customContentUrl,
      tilesUrl,
    );

    // Build the response data
    const responseData = updatedCustomContent || {
      error: 'Failed to update custom content',
      sectionNames,
    };

    res.statusCode = updatedCustomContent ? 200 : 500;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify(responseData, null, 2));
  } catch (error) {
    console.error('[API Routes] ❌ Error processing custom content update:', error);
    res.statusCode = 500;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify({
      error: 'Failed to process request',
      message: error instanceof Error ? error.message : String(error),
    }));
  }
}

/**
 * Serve a client file (JS or CSS)
 */
async function serveClientFile(
  res: ServerResponse,
  sectionName: string,
  filePath: string,
  contentType: string,
): Promise<void> {
  try {
    const distPath = path.resolve(process.cwd(), 'dist');
    const fullPath = path.join(distPath, 'sections', sectionName, 'js', 'main', 'client', filePath);

    // Security: prevent directory traversal
    const resolvedPath = path.resolve(fullPath);
    const basePath = path.resolve(distPath, 'sections', sectionName);
    if (!resolvedPath.startsWith(basePath)) {
      res.statusCode = 403;
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify({ error: 'Access denied' }));
      return;
    }

    if (!fs.existsSync(resolvedPath)) {
      res.statusCode = 404;
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify({ error: 'File not found', file: filePath, section: sectionName }));
      return;
    }

    const fileContent = fs.readFileSync(resolvedPath, 'utf-8');
    res.statusCode = 200;
    res.setHeader('Content-Type', contentType);
    setNoCacheHeaders(res);
    res.end(fileContent);
  } catch (error) {
    console.error('[API Routes] ❌ Error serving client file:', error);
    res.statusCode = 500;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify({
      error: 'Failed to serve file',
      message: error instanceof Error ? error.message : String(error),
    }));
  }
}

/**
 * Handle GET /api/v1/client-js
 * Returns client.js for a specific section
 */
async function handleGetClientJs(
  _req: IncomingMessage,
  res: ServerResponse,
  sectionName: string,
): Promise<void> {
  await serveClientFile(res, sectionName, 'client.js', 'application/javascript');
}

/**
 * Handle GET /api/v1/client-css
 * Returns client.css for a specific section
 */
async function handleGetClientCss(
  _req: IncomingMessage,
  res: ServerResponse,
  sectionName: string,
): Promise<void> {
  await serveClientFile(res, sectionName, 'assets/client.css', 'text/css');
}

/**
 * Handle GET /api/v1/assets
 * Returns assets for a specific section
 */
async function handleGetAssets(
  req: IncomingMessage,
  res: ServerResponse,
  sectionName: string,
): Promise<void> {
  try {
    const url = req.url || '';
    const filePath = getQueryParam(url, 'file');

    if (!filePath) {
      res.statusCode = 400;
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify({ error: 'Missing file parameter' }));
      return;
    }

    // Security: prevent directory traversal
    if (filePath.includes('..') || filePath.startsWith('/')) {
      res.statusCode = 403;
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify({ error: 'Access denied' }));
      return;
    }

    const distPath = path.resolve(process.cwd(), 'dist');
    const fullPath = path.join(distPath, 'sections', sectionName, 'assets', filePath);
    const resolvedPath = path.resolve(fullPath);
    const basePath = path.resolve(distPath, 'sections', sectionName, 'assets');

    if (!resolvedPath.startsWith(basePath)) {
      res.statusCode = 403;
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify({ error: 'Access denied' }));
      return;
    }

    if (!fs.existsSync(resolvedPath)) {
      res.statusCode = 404;
      res.setHeader('Content-Type', 'application/json');
      setNoCacheHeaders(res);
      res.end(JSON.stringify({ error: 'Asset not found', file: filePath, section: sectionName }));
      return;
    }

    // Detect content type based on file extension
    const ext = path.extname(filePath).toLowerCase();
    const contentTypeMap: { [key: string]: string } = {
      '.png': 'image/png',
      '.jpg': 'image/jpeg',
      '.jpeg': 'image/jpeg',
      '.gif': 'image/gif',
      '.webp': 'image/webp',
      '.svg': 'image/svg+xml',
      '.ico': 'image/x-icon',
      '.woff': 'font/woff',
      '.woff2': 'font/woff2',
      '.ttf': 'font/ttf',
      '.eot': 'application/vnd.ms-fontobject',
      '.json': 'application/json',
      '.css': 'text/css',
      '.js': 'application/javascript',
    };

    const contentType = contentTypeMap[ext] || 'application/octet-stream';
    const fileContent = fs.readFileSync(resolvedPath);

    res.statusCode = 200;
    res.setHeader('Content-Type', contentType);
    setNoCacheHeaders(res);
    res.end(fileContent);
  } catch (error) {
    console.error('[API Routes] ❌ Error serving asset:', error);
    res.statusCode = 500;
    res.setHeader('Content-Type', 'application/json');
    setNoCacheHeaders(res);
    res.end(JSON.stringify({
      error: 'Failed to serve asset',
      message: error instanceof Error ? error.message : String(error),
    }));
  }
}

/**
 * Main API router
 * Routes requests to appropriate handlers
 * Extracts section name and original_url from query parameters if available
 * Resolves showcase ID from dist folder based on available showcases
 */
export function handleApiRequest(
  req: IncomingMessage,
  res: ServerResponse,
  authToken?: string,
): void {
  const url = req.url || '';

  if (url.startsWith('/api/v1/sections')) {
    handleGetSections(req, res);
    return;
  }

  // Extract section name from query parameter if provided
  const sectionName = getQueryParam(url, 'section') || '';

  // Extract original_url from query parameter if provided
  const originalUrl = getQueryParam(url, 'original_url') || '';

  if (url.startsWith('/api/v1/tile')) {
    // Extract section names from query parameter (comma-separated)
    const sectionNamesParam = getQueryParam(url, 'sections');
    const sectionNames = sectionNamesParam ? sectionNamesParam.split(',').map(s => s.trim()) : [];
    handleTile(req, res, sectionNames, authToken, originalUrl);
    return;
  }

  if (url.startsWith('/api/v1/custom_content')) {
    // Extract section names from query parameter (comma-separated)
    const sectionNamesParam = getQueryParam(url, 'sections');
    const sectionNames = sectionNamesParam ? sectionNamesParam.split(',').map(s => s.trim()) : [];
    handleUpdateCustomContent(req, res, sectionNames, authToken, originalUrl);
    return;
  }

  if (url.startsWith('/api/v1/client-js')) {
    handleGetClientJs(req, res, sectionName);
    return;
  }

  if (url.startsWith('/api/v1/client-css')) {
    handleGetClientCss(req, res, sectionName);
    return;
  }

  if (url.startsWith('/api/v1/assets')) {
    handleGetAssets(req, res, sectionName);
    return;
  }

  // Unknown API route
  res.statusCode = 404;
  res.setHeader('Content-Type', 'application/json');
  setNoCacheHeaders(res);
  res.end(JSON.stringify({
    error: 'API route not found',
    url,
  }));
}
