import { spawn } from 'node:child_process';
import path from 'node:path';
import { env } from '../config/env.js';
import { db } from '../db/client.js';
import { CHAT_CONVERSATION_REQUEST_TABLE } from './chat-room-service.js';

function extractDiffBlocks(text: string) {
  return Array.from(String(text ?? '').matchAll(/```diff[^\n]*\n([\s\S]*?)\n```/g))
    .map((match) => (typeof match[1] === 'string' ? match[1].trim() : ''))
    .filter(Boolean);
}

function resolveChatRepoPath() {
  const repoPath = env.SERVER_COMMAND_MAIN_PROJECT_ROOT || env.PLAN_MAIN_PROJECT_REPO_PATH || env.PLAN_GIT_REPO_PATH;

  if (!repoPath?.trim()) {
    throw new Error('롤백 대상 저장소 경로가 설정되지 않았습니다.');
  }

  return path.resolve(repoPath);
}

function summarizeGitApplyError(stderr: string) {
  const normalized = String(stderr ?? '')
    .split('\n')
    .map((line) => line.trim())
    .filter(Boolean);

  if (normalized.length === 0) {
    return '현재 작업트리가 이 요청 시점과 달라 자동 롤백을 적용할 수 없습니다.';
  }

  return normalized.slice(0, 3).join('\n');
}

function stripChatResourcePrefix(candidate: string) {
  let normalized = String(candidate ?? '').replace(/\\/g, '/');
  const marker = '/resource/';

  while (normalized.includes(marker)) {
    normalized = normalized.slice(normalized.indexOf(marker) + marker.length);
  }

  return normalized.replace(/^\/+/, '');
}

function normalizeRollbackDiffLine(line: string) {
  const normalizedLine = String(line ?? '');

  if (normalizedLine.startsWith('diff --git ')) {
    return normalizedLine.replace(/^diff --git a\/(.+?) b\/(.+)$/, (_match, left, right) => {
      return `diff --git a/${stripChatResourcePrefix(left)} b/${stripChatResourcePrefix(right)}`;
    });
  }

  if (normalizedLine.startsWith('--- a/')) {
    return `--- a/${stripChatResourcePrefix(normalizedLine.slice('--- a/'.length))}`;
  }

  if (normalizedLine.startsWith('+++ b/')) {
    return `+++ b/${stripChatResourcePrefix(normalizedLine.slice('+++ b/'.length))}`;
  }

  if (normalizedLine.startsWith('rename from ')) {
    return `rename from ${stripChatResourcePrefix(normalizedLine.slice('rename from '.length))}`;
  }

  if (normalizedLine.startsWith('rename to ')) {
    return `rename to ${stripChatResourcePrefix(normalizedLine.slice('rename to '.length))}`;
  }

  return normalizedLine;
}

export function normalizeRollbackDiffText(diffText: string) {
  return String(diffText ?? '')
    .split('\n')
    .map((line) => normalizeRollbackDiffLine(line))
    .join('\n');
}

async function runGitApplyReverse(repoPath: string, diffText: string, checkOnly: boolean) {
  return await new Promise<{ ok: boolean; stderr: string }>((resolve, reject) => {
    const args = ['apply', '--reverse', '--whitespace=nowarn'];

    if (checkOnly) {
      args.push('--check');
    }

    args.push('-');

    const child = spawn('git', args, {
      cwd: repoPath,
      stdio: ['pipe', 'ignore', 'pipe'],
    });

    let stderr = '';

    child.stderr.on('data', (chunk) => {
      stderr += String(chunk ?? '');
    });

    child.on('error', (error) => {
      reject(error);
    });

    child.on('close', (code) => {
      resolve({
        ok: code === 0,
        stderr,
      });
    });

    child.stdin.write(diffText);
    child.stdin.end();
  });
}

export async function rollbackChatRuntimeRequest(args: { requestId: string; sessionId?: string | null }) {
  const normalizedRequestId = args.requestId.trim();
  const normalizedSessionId = args.sessionId?.trim() || null;

  if (!normalizedRequestId) {
    throw new Error('롤백할 요청 ID가 비어 있습니다.');
  }

  const repoPath = resolveChatRepoPath();
  const row = await db(CHAT_CONVERSATION_REQUEST_TABLE)
    .select('session_id', 'request_id', 'status', 'response_text')
    .where('request_id', normalizedRequestId)
    .modify((query) => {
      if (normalizedSessionId) {
        query.andWhere('session_id', normalizedSessionId);
      }
    })
    .orderBy('updated_at', 'desc')
    .first();

  if (!row) {
    throw new Error('롤백할 Codex Live 실행 기록을 찾지 못했습니다.');
  }

  const status = String(row.status ?? '').trim();

  if (status === 'cancelled' || status === 'removed') {
    throw new Error('취소되었거나 제거된 요청은 롤백할 수 없습니다.');
  }

  const diffBlocks = extractDiffBlocks(String(row.response_text ?? ''));

  if (diffBlocks.length === 0) {
    throw new Error('이 요청에는 롤백 가능한 diff 기록이 없습니다.');
  }

  const diffText = normalizeRollbackDiffText(`${diffBlocks.join('\n\n')}\n`);
  const checkResult = await runGitApplyReverse(repoPath, diffText, true);

  if (!checkResult.ok) {
    throw new Error(summarizeGitApplyError(checkResult.stderr));
  }

  const applyResult = await runGitApplyReverse(repoPath, diffText, false);

  if (!applyResult.ok) {
    throw new Error(summarizeGitApplyError(applyResult.stderr));
  }

  return {
    rolledBack: true,
    requestId: normalizedRequestId,
    sessionId: String(row.session_id ?? normalizedSessionId ?? ''),
    diffBlockCount: diffBlocks.length,
  };
}
