import { NextRequest, NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
import sharp from 'sharp';

/** Walk up from cwd until we find backend + frontend package.json (monorepo root). */
function findMonorepoRoot(start: string): string {
  let dir = path.resolve(start);
  for (let i = 0; i < 8; i++) {
    if (
      fs.existsSync(path.join(dir, 'backend', 'package.json')) &&
      fs.existsSync(path.join(dir, 'frontend', 'package.json'))
    ) {
      return dir;
    }
    const parent = path.dirname(dir);
    if (parent === dir) break;
    dir = parent;
  }
  return path.resolve(start, '..');
}

/**
 * Resolve relative MEDIA_STORAGE_PATH against monorepo root — matches
 * `backend/src/api/media.ts` (REPO_ROOT + relative path).
 */
function resolveMediaStoragePathFromEnv(envPath: string): string {
  if (path.isAbsolute(envPath)) {
    return envPath;
  }
  return path.resolve(findMonorepoRoot(process.cwd()), envPath);
}

/** Resolve at request time so systemd / production env is visible (avoid build-time inlining). */
function getMediaStorageRoot(): string {
  const envPath = process.env['MEDIA_STORAGE_PATH'];
  if (envPath) {
    return resolveMediaStoragePathFromEnv(envPath);
  }
  const root = findMonorepoRoot(process.cwd());
  // Prefer repo-root `storage/media` first — matches backend default in `backend/src/api/media.ts`.
  const candidates = [
    path.join(root, 'storage', 'media'),
    path.join(root, 'backend', 'storage', 'media'),
  ];
  for (const dir of candidates) {
    if (fs.existsSync(dir)) {
      return dir;
    }
  }
  return candidates[0];
}

const CACHE_HEADERS = {
  'Cache-Control': 'public, max-age=31536000, immutable',
};

interface MediaMeta {
  mimeType: string;
  type: 'image' | 'video';
  size: number;
}

function readMeta(root: string, id: string): MediaMeta | null {
  const metaPath = path.join(root, `${id}.json`);
  try {
    const raw = fs.readFileSync(metaPath, 'utf-8');
    return JSON.parse(raw);
  } catch {
    return null;
  }
}

function fileExists(filePath: string): boolean {
  try {
    fs.accessSync(filePath, fs.constants.R_OK);
    return true;
  } catch {
    return false;
  }
}

async function handleImageResize(
  root: string,
  filePath: string,
  width: number,
  meta: MediaMeta,
  id: string
): Promise<NextResponse> {
  const cachePath = path.join(root, 'cache');
  fs.mkdirSync(cachePath, { recursive: true });

  const cacheKey = `${id}_w${width}`;
  const cachedFile = path.join(cachePath, cacheKey);

  if (fileExists(cachedFile)) {
    const cached = fs.readFileSync(cachedFile);
    return new NextResponse(new Uint8Array(cached), {
      status: 200,
      headers: {
        'Content-Type': meta.mimeType,
        'Content-Length': String(cached.length),
        ...CACHE_HEADERS,
      },
    });
  }

  const buffer = fs.readFileSync(filePath);
  const resized = await sharp(buffer)
    .resize({ width, withoutEnlargement: true })
    .toBuffer();

  fs.writeFileSync(cachedFile, resized);

  return new NextResponse(new Uint8Array(resized), {
    status: 200,
    headers: {
      'Content-Type': meta.mimeType,
      'Content-Length': String(resized.length),
      ...CACHE_HEADERS,
    },
  });
}

function handleVideoStream(
  filePath: string,
  meta: MediaMeta,
  rangeHeader: string | null
): NextResponse {
  const stat = fs.statSync(filePath);
  const fileSize = stat.size;

  if (rangeHeader) {
    const parts = rangeHeader.replace(/bytes=/, '').split('-');
    const start = parseInt(parts[0], 10);
    const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
    const chunkSize = end - start + 1;

    const stream = fs.createReadStream(filePath, { start, end });
    const readable = new ReadableStream({
      start(controller) {
        stream.on('data', (chunk: string | Buffer) => controller.enqueue(chunk as Buffer));
        stream.on('end', () => controller.close());
        stream.on('error', (err) => controller.error(err));
      },
    });

    return new NextResponse(readable as any, {
      status: 206,
      headers: {
        'Content-Range': `bytes ${start}-${end}/${fileSize}`,
        'Accept-Ranges': 'bytes',
        'Content-Length': String(chunkSize),
        'Content-Type': meta.mimeType,
        ...CACHE_HEADERS,
      },
    });
  }

  const stream = fs.createReadStream(filePath);
  const readable = new ReadableStream({
    start(controller) {
      stream.on('data', (chunk: string | Buffer) => controller.enqueue(chunk as Buffer));
      stream.on('end', () => controller.close());
      stream.on('error', (err) => controller.error(err));
    },
  });

  return new NextResponse(readable as any, {
    status: 200,
    headers: {
      'Accept-Ranges': 'bytes',
      'Content-Length': String(fileSize),
      'Content-Type': meta.mimeType,
      ...CACHE_HEADERS,
    },
  });
}

export const dynamic = 'force-dynamic';
export const runtime = 'nodejs';

export async function GET(
  request: NextRequest,
  context: { params: Promise<{ id: string }> | { id: string } }
) {
  const params = await Promise.resolve(context.params);
  const id = params.id;
  if (!id) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 });
  }

  const root = getMediaStorageRoot();
  const filePath = path.join(root, id);
  if (!fileExists(filePath)) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 });
  }

  const meta = readMeta(root, id);
  if (!meta) {
    return NextResponse.json({ error: 'Metadata not found' }, { status: 404 });
  }

  const { searchParams } = new URL(request.url);
  const widthParam = searchParams.get('w');

  if (meta.type === 'image' && widthParam) {
    const width = parseInt(widthParam, 10);
    if (isNaN(width) || width < 1 || width > 4096) {
      return NextResponse.json({ error: 'Invalid width' }, { status: 400 });
    }
    return handleImageResize(root, filePath, width, meta, id);
  }

  if (meta.type === 'video') {
    const rangeHeader = request.headers.get('range');
    return handleVideoStream(filePath, meta, rangeHeader);
  }

  const buffer = fs.readFileSync(filePath);
  return new NextResponse(new Uint8Array(buffer), {
    status: 200,
    headers: {
      'Content-Type': meta.mimeType,
      'Content-Length': String(buffer.length),
      ...CACHE_HEADERS,
    },
  });
}
