import express from 'express';
import path from 'path';
import fs from 'fs';
import { EvaluationLicenseRequestsService } from '../services/evaluationService';
import { FileService } from '../services/fileService';
import { formatDate, formatSize } from '../utils/format';
import { getEnvList } from '../utils/env';
import { matchesAnyPattern } from '../utils/patternMatch';
import {
  isAuthenticated,
  checkBasicAuth,
} from '../middleware/basicAuth';

export class HomeController {
  public static async index(req: express.Request, res: express.Response) {
    // root listing
    await HomeController.handlePathRequest('', req, res);
  }

  public static async browseOrDownload(
    req: express.Request,
    res: express.Response,
  ) {
    // decode path without leading slash
    const requestedPath = decodeURIComponent(req.path.replace(/^\//, ''));
    await HomeController.handlePathRequest(requestedPath, req, res);
  }

  private static async handlePathRequest(
    requestedPath: string,
    req: express.Request,
    res: express.Response,
  ): Promise<void> {
    const reqPath = requestedPath;
    const isPrivate = HomeController.isPrivatePath(reqPath);

    // Always validate auth (silently) to set authenticated flag for listings
    // Only require auth (with 401) when accessing private paths
    if (isPrivate) {
      if (!checkBasicAuth(req, res)) {
        return; // Response already sent
      }
    } else {
      // Credentials already validated upstream by pre-validation middleware in index.ts
    }

    await HomeController.handlePathRequestInternal(reqPath, req, res);
  }

  private static async handlePathRequestInternal(
    requestedPath: string,
    req: express.Request,
    res: express.Response,
  ): Promise<void> {
    const reqPath = requestedPath;
    const safeRootPath = FileService.resolveSafePath(reqPath);
    const protectedRoot = FileService.getProtectedRoot();
    const protectedByName = new Set(getEnvList('PROTECTED_FILES'));
    const protectedDirs = new Set(getEnvList('PROTECTED_DIRS'));

    // We will only look in protected root for root-level protected filenames
    // (e.g., ViewMate_Setup.zip). Subfolder downloads remain served from downloads root.
    let finalPath: string | null = null;
    let isProtected = false;

    // If directory, render its listing
    if (
      safeRootPath &&
      fs.existsSync(safeRootPath) &&
      fs.statSync(safeRootPath).isDirectory()
    ) {
      const fsEntriesRaw = FileService.listDirectory(`/${reqPath}`);

      // Filter hidden objects (always) and private folders (unless authenticated)
      const filteredEntries = fsEntriesRaw.filter((entry) => {
        // Hide if matches hidden patterns
        if (HomeController.isHidden(entry.name, reqPath)) {
          return false;
        }

        // Hide private folders unless authenticated
        const privateFolders = getEnvList('PRIVATE_FOLDERS');
        if (
          privateFolders.length > 0 &&
          entry.isDirectory &&
          matchesAnyPattern(entry.name, privateFolders) &&
          !isAuthenticated(req)
        ) {
          return false;
        }

        return true;
      });

      // Join root files with protected-root files (files only), then sort
      if (reqPath === '' || reqPath === '/') {
        try {
          if (
            fs.existsSync(protectedRoot) &&
            fs.statSync(protectedRoot).isDirectory()
          ) {
            fs.readdirSync(protectedRoot).forEach((entryName) => {
              // Skip hidden files from protected root too
              if (HomeController.isHidden(entryName)) {
                return;
              }
              const full = path.join(protectedRoot, entryName);
              const st = fs.statSync(full);
              if (st.isFile()) {
                filteredEntries.push({
                  name: entryName,
                  isDirectory: false,
                  size: st.size,
                  mtime: st.mtime,
                });
              }
            });
          }
        } catch (_) {
          // ignore protected root read errors silently
        }
      }

      const collator = new Intl.Collator(undefined, {
        numeric: true,
        sensitivity: 'base',
      });
      filteredEntries.sort((a, b) => collator.compare(a.name, b.name));
      const fsEntries = filteredEntries.map((e) => ({
        name: e.name,
        isDirectory: e.isDirectory,
        size: e.size,
        mtime: e.mtime,
        mtimeFormatted: formatDate(e.mtime),
        sizeFormatted: e.isDirectory ? '-' : formatSize(e.size),
      }));

      const currentDirPath =
        reqPath.endsWith('/') || reqPath === '' ? reqPath : `${reqPath}/`;
      const parentDirPath =
        currentDirPath === '' ? '' : currentDirPath.replace(/[^/]+\/?$/, '');

      res.render('pages/index', {
        title: 'Pentalogix Downloads',
        fsEntries,
        currentDirPath,
        parentDirPath,
      });
      return;
    }

    const baseName = path.basename(reqPath);
    const firstSegment = reqPath.split('/')[0] || '';
    const hasSubdir = reqPath.includes('/');
    const isNameProtected =
      protectedByName.has(baseName) || protectedDirs.has(firstSegment);

    // Block access to hidden files/directories
    if (HomeController.isHidden(baseName, reqPath)) {
      res.status(404).render('pages/404', { title: 'Page Not Found' });
      return;
    }

    if (
      safeRootPath &&
      fs.existsSync(safeRootPath) &&
      fs.statSync(safeRootPath).isFile()
    ) {
      // Serve from downloads, but require token if name/dir is protected
      finalPath = safeRootPath;
      isProtected = isNameProtected;
    } else if (!hasSubdir && protectedByName.has(baseName)) {
      // Root-level protected file may live only in PROTECTED_DIR
      const candidate = FileService.resolveProtectedPath(baseName);
      if (
        candidate &&
        fs.existsSync(candidate) &&
        fs.statSync(candidate).isFile()
      ) {
        finalPath = candidate;
        isProtected = true;
      }
    }

    if (!finalPath) {
      res.status(404).render('pages/404', { title: 'Page Not Found' });
      return;
    }

    if (isProtected) {
      const token = req.query.token as string | undefined;
      if (!token) {
        res.status(401).send('Unauthorized');
        return;
      }

      const match = await EvaluationLicenseRequestsService.findBySecret(token);

      if (!match) {
        res.status(500).send('Invalid token');
        return;
      }

      if (match.access_counter >= 3) {
        res.status(403).send('Maximum access reached');
        return;
      }

      await EvaluationLicenseRequestsService.incrementAccessCounter(match.id);
    }

    res.download(finalPath);
  }

  private static isPrivatePath(requestedPath: string): boolean {
    const privateFolders = getEnvList('PRIVATE_FOLDERS');
    if (privateFolders.length === 0) return false;
    const firstSegment = requestedPath.split('/')[0] || '';
    return matchesAnyPattern(firstSegment, privateFolders);
  }

  private static isHidden(name: string, relativePath: string = ''): boolean {
    const hiddenPatterns = getEnvList('HIDDEN_OBJECTS');
    if (hiddenPatterns.length === 0) return false;

    // Build full path for patterns like **/.*
    const fullPath = relativePath ? `${relativePath}/${name}` : name;

    // Check both name and full path against patterns
    return (
      matchesAnyPattern(name, hiddenPatterns) ||
      matchesAnyPattern(fullPath, hiddenPatterns)
    );
  }
}
