/**
 * kordoc 마이크로서비스 — HWP/HWPX/PDF/텍스트 → Markdown 변환 API
 */

import express from 'express';
import fs from 'fs';
import path from 'path';
import { parse, detectFormat } from 'kordoc';

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

// 동시 파싱 제한 — kordoc parse()는 취소 불가(AbortSignal 미지원)이므로
// Promise.race 타임아웃 후 유령 작업이 남을 수 있음. 슬롯 제한으로 누적 방지.
const MAX_CONCURRENT_PARSES = 2;
const parseJobs = new Map();  // parseId → { state: "running"|"timed_out" }

function getParseTimeoutMs(fileSize) {
  // 10MB당 60초, 최소 60초, 최대 300초 (extract_worker와 동일 공식)
  return Math.min(300000, Math.max(60000, Math.floor(fileSize / (10 * 1024 * 1024)) * 60000 + 60000));
}

function releaseParseSlot(parseId) {
  if (parseJobs.has(parseId)) {
    parseJobs.delete(parseId);
  }
}

app.use(express.json({ limit: '500mb' }));

// 헬스체크
app.get('/health', (req, res) => {
  res.json({ status: 'ok', service: 'kordoc' });
});

// 지원 포맷 목록
const TEXT_FORMATS = new Set(['.md', '.txt', '.csv', '.json', '.xml', '.html']);
const PARSEABLE_FORMATS = new Set(['.hwp', '.hwpx', '.pdf']);
const IMAGE_FORMATS = new Set(['.jpg', '.jpeg', '.png', '.tiff', '.tif', '.bmp', '.gif']);

/**
 * 문서 파싱 — 파일 경로를 받아 Markdown으로 변환
 */
app.post('/parse', async (req, res) => {
  const startTime = Date.now();

  try {
    const { filePath } = req.body;
    if (!filePath) {
      return res.status(400).json({ error: 'filePath is required' });
    }

    // 파일 존재 확인
    if (!fs.existsSync(filePath)) {
      return res.status(404).json({ error: `파일을 찾을 수 없습니다: ${filePath}` });
    }

    const ext = path.extname(filePath).toLowerCase();
    const stat = fs.statSync(filePath);

    // 100MB 초과 파일 거부
    if (stat.size > 100 * 1024 * 1024) {
      return res.status(413).json({ error: '파일 크기 100MB 초과' });
    }

    // 텍스트 파일 — 직접 읽기
    if (TEXT_FORMATS.has(ext)) {
      const text = fs.readFileSync(filePath, 'utf-8');
      return res.json({
        success: true,
        markdown: text,
        metadata: { format: ext.slice(1), fileSize: stat.size },
        format: ext.slice(1),
        requires_ocr: false,
      });
    }

    // 이미지 파일 — OCR 필요 플래그
    if (IMAGE_FORMATS.has(ext)) {
      return res.json({
        success: true,
        markdown: '',
        metadata: { format: ext.slice(1), fileSize: stat.size },
        format: ext.slice(1),
        requires_ocr: true,
      });
    }

    // HWP/HWPX/PDF — kordoc 파싱
    if (PARSEABLE_FORMATS.has(ext)) {
      if (parseJobs.size >= MAX_CONCURRENT_PARSES) {
        console.warn(`[PARSE] REJECTED ${filePath} — concurrent limit (${parseJobs.size}/${MAX_CONCURRENT_PARSES})`);
        return res.status(503).json({ error: `동시 파싱 한도 초과 (${MAX_CONCURRENT_PARSES}건), 재시도 필요` });
      }

      const buffer = fs.readFileSync(filePath);
      const parseTimeout = getParseTimeoutMs(stat.size);
      const parseId = `${path.basename(filePath)}-${Date.now()}`;

      parseJobs.set(parseId, { state: 'running' });
      console.log(`[PARSE] START ${parseId} ext=${ext} size=${(stat.size / (1024 * 1024)).toFixed(1)}MB timeout=${parseTimeout / 1000}s active=${parseJobs.size}`);

      let timeoutHandle;
      try {
        const result = await Promise.race([
          parse(buffer).finally(() => {
            // timed_out 상태에서만 여기서 슬롯 해제 — 정상 경로는 아래에서 처리
            const job = parseJobs.get(parseId);
            if (job && job.state === 'timed_out') {
              releaseParseSlot(parseId);
              console.warn(`[PARSE] ZOMBIE_DONE ${parseId} active=${parseJobs.size}`);
            }
          }),
          new Promise((_, reject) => {
            timeoutHandle = setTimeout(() => {
              const job = parseJobs.get(parseId);
              if (job) job.state = 'timed_out';
              // 슬롯은 유령 완료 시점(finally)에서 해제 — 여기서 빼지 않음
              reject(new Error(`파싱 타임아웃 (${parseTimeout / 1000}초, ${(stat.size / (1024 * 1024)).toFixed(1)}MB, ext=${ext.slice(1)})`));
            }, parseTimeout);
          }),
        ]);

        // 정상 완료: 타이머 정리 + 슬롯 해제
        clearTimeout(timeoutHandle);
        releaseParseSlot(parseId);
        const elapsed = Date.now() - startTime;
        console.log(`[PARSE] DONE ${parseId} elapsed=${elapsed}ms chars=${(result.markdown || '').length} active=${parseJobs.size}`);

        if (!result.success) {
          return res.status(422).json({
            error: '문서 파싱 실패',
            warnings: result.warnings || [],
          });
        }

        return res.json({
          success: true,
          markdown: result.markdown || '',
          metadata: {
            ...(result.metadata || {}),
            format: ext.slice(1),
            fileSize: stat.size,
            parseTime: elapsed,
          },
          format: ext.slice(1),
          requires_ocr: false,
        });
      } catch (parseErr) {
        // 비-timeout 에러: 타이머 정리 + 슬롯 해제
        const job = parseJobs.get(parseId);
        if (job && job.state !== 'timed_out') {
          clearTimeout(timeoutHandle);
          releaseParseSlot(parseId);
        }
        // timeout 에러: 타이머는 이미 발화됨, 슬롯은 parse().finally()에서 해제 예정
        throw parseErr;
      }
    }

    // 미지원 포맷
    return res.json({
      success: true,
      markdown: '',
      metadata: { format: ext.slice(1), fileSize: stat.size },
      format: ext.slice(1),
      requires_ocr: false,
      unsupported: true,
    });
  } catch (err) {
    console.error(`[ERROR] /parse: ${err.message}`);
    res.status(500).json({ error: err.message });
  }
});

// 문서 비교
app.post('/compare', async (req, res) => {
  try {
    const { filePathA, filePathB } = req.body;
    if (!filePathA || !filePathB) {
      return res.status(400).json({ error: 'filePathA and filePathB are required' });
    }

    // TODO: Phase 2에서 kordoc compare 구현
    return res.json({ diffs: [], message: 'compare는 Phase 2에서 구현 예정' });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(PORT, () => {
  console.log(`kordoc-service listening on port ${PORT}`);
});
