// 이 파일이 하는 일: 코딩 문제 풀이 화면 — 문제를 읽고, 코드를 짜고, 실행해 보고, 제출한다.
//
// 언어별로 흐름이 다르다:
//  - JS   : [실행] 누르면 브라우저(Web Worker)가 테스트를 돌려 즉시 통과/실패를 보여준다.
//           [제출]하면 코드 + 채점 결과가 서버에 저장되고 멘토가 본다.
//  - SQL  : [실행] 누르면 브라우저 안의 SQLite(sql.js)가 쿼리를 돌려 결과 표를 보여주고,
//           기대 결과와 비교해 통과/실패를 가린다. 서버 DB는 전혀 건드리지 않는다.
//  - JAVA : 실행 버튼이 없다. 코드를 제출하면 멘토가 읽고 피드백한다.
import { useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
import client from '../api/client';
import CodeEditor from '../components/CodeEditor';
import { runJs } from '../lib/runJs';
import { runSql, gradeSql } from '../lib/runSql';

const DIFFICULTY_LABEL = { EASY: '쉬움', NORMAL: '보통', HARD: '어려움' };
const LANGUAGE_LABEL = { JS: 'JavaScript', SQL: 'SQL', JAVA: 'Java' };

/** SQL 실행 결과를 표로 보여주는 작은 부품 (실행 결과·기대 결과 양쪽에서 쓴다) */
function SqlResultTable({ columns, rows }) {
  return (
    <div style={{ overflowX: 'auto' }}>
      <table className="sql-table">
        <thead>
          <tr>{columns.map((c, i) => <th key={i}>{c}</th>)}</tr>
        </thead>
        <tbody>
          {rows.map((row, i) => (
            <tr key={i}>
              {row.map((cell, j) => <td key={j}>{cell === null ? 'NULL' : String(cell)}</td>)}
            </tr>
          ))}
        </tbody>
      </table>
      <p className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>{rows.length}행</p>
    </div>
  );
}

export default function CodingProblemPage() {
  const { id } = useParams();

  const [problem, setProblem] = useState(null);
  const [code, setCode] = useState('');
  const [results, setResults] = useState(null); // 실행 결과 [{name, passed, expected, actual}]
  const [sqlOutput, setSqlOutput] = useState(null); // SQL 실행 결과 표 {columns, rows, passed}
  const [runError, setRunError] = useState(''); // 문법 오류·타임아웃 등
  const [loading, setLoading] = useState(true);
  const [running, setRunning] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [message, setMessage] = useState('');
  const [error, setError] = useState('');

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    client
      .get(`/coding/problems/${id}`)
      .then((res) => {
        if (cancelled) return;
        const data = res.data;
        setProblem(data);
        // 전에 제출한 코드가 있으면 그걸 이어서, 없으면 뼈대 코드로 시작한다.
        setCode(data.myCode ?? data.starterCode);
        // 전에 낸 채점 결과가 있으면 복원해서 보여준다.
        if (data.resultsJson) {
          try {
            setResults(JSON.parse(data.resultsJson));
          } catch {
            // 저장된 결과가 깨져 있어도 화면은 떠야 한다 — 무시하고 넘어간다.
          }
        }
      })
      .catch(() => {
        if (!cancelled) setError('문제를 불러오지 못했어요.');
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [id]);

  const isJs = problem?.language === 'JS';
  const isSql = problem?.language === 'SQL';
  // testsJson은 언어마다 모양이 다르다 — JS는 테스트 배열, SQL은 기대 결과 객체
  const tests = isJs && problem?.testsJson ? JSON.parse(problem.testsJson) : [];
  const sqlExpected = isSql && problem?.testsJson ? JSON.parse(problem.testsJson) : null;

  /**
   * SQL을 실행하고 채점한다. [실행]과 [제출] 양쪽에서 쓴다.
   * 결과 요약은 JS와 같은 형식([{name, passed, ...}])으로도 만든다 —
   * 서버 저장·멘토 화면·재방문 복원이 언어 구분 없이 한 가지 형식으로 굴러가게 하기 위해서다.
   */
  async function runAndGradeSql() {
    const outcome = await runSql(problem.setupSql, code);
    if (outcome.error) {
      return { error: outcome.error };
    }
    const passed = gradeSql(outcome, sqlExpected);
    const summary = [{
      name: '쿼리 결과가 기대 결과와 일치',
      passed,
      expected: `${sqlExpected.expectedRows.length}행`,
      actual: `${outcome.rows.length}행`,
    }];
    return { outcome: { ...outcome, passed }, summary };
  }

  /** [실행] — 브라우저에서 테스트를 돌린다 (서버에 저장하지 않는다) */
  async function handleRun() {
    setRunning(true);
    setRunError('');
    setMessage('');
    if (isSql) {
      const { error: sqlError, outcome, summary } = await runAndGradeSql();
      if (sqlError) {
        setRunError(sqlError);
        setSqlOutput(null);
        setResults(null);
      } else {
        setSqlOutput(outcome);
        setResults(summary);
      }
      setRunning(false);
      return;
    }
    const outcome = await runJs(code, problem.functionName, tests);
    if (outcome.error) {
      setRunError(outcome.error);
      setResults(null);
    } else {
      setResults(outcome.results);
    }
    setRunning(false);
  }

  /** [제출] — JS면 먼저 채점하고, 코드와 결과를 함께 서버에 저장한다 */
  async function handleSubmit() {
    setSubmitting(true);
    setMessage('');
    setError('');
    try {
      let payload = { code, passedCount: null, totalCount: null, resultsJson: null };

      if (isSql) {
        const { error: sqlError, outcome, summary } = await runAndGradeSql();
        if (sqlError) {
          // SQL이 아예 안 도는 상태로는 제출하지 않는다 — JS의 문법 오류 차단과 같은 이유.
          setRunError(sqlError);
          setSqlOutput(null);
          setSubmitting(false);
          return;
        }
        setSqlOutput(outcome);
        setResults(summary);
        payload = {
          code,
          passedCount: outcome.passed ? 1 : 0,
          totalCount: 1,
          resultsJson: JSON.stringify(summary),
        };
      } else if (isJs) {
        const outcome = await runJs(code, problem.functionName, tests);
        if (outcome.error) {
          // 문법 오류가 있으면 제출을 막는다 — 멘토에게 안 돌아가는 코드를 보내지 않도록.
          setRunError(outcome.error);
          setResults(null);
          setSubmitting(false);
          return;
        }
        setResults(outcome.results);
        const passed = outcome.results.filter((r) => r.passed).length;
        payload = {
          code,
          passedCount: passed,
          totalCount: outcome.results.length,
          resultsJson: JSON.stringify(outcome.results),
        };
      }

      const res = await client.post(`/coding/problems/${id}/submit`, payload);
      setProblem((prev) => ({ ...prev, ...res.data, feedback: null }));
      setMessage(
        res.data.status === 'PASSED'
          ? '통과! 제출했습니다. 멘토가 코드를 확인합니다.'
          : '제출했습니다. 멘토가 코드를 읽고 피드백을 남길 거예요.'
      );
    } catch {
      setError('제출하지 못했어요. 잠시 뒤 다시 시도해 주세요.');
    } finally {
      setSubmitting(false);
    }
  }

  if (loading) {
    return <p className="empty">불러오는 중…</p>;
  }
  if (!problem) {
    return <p className="error-text">{error || '문제를 찾을 수 없습니다.'}</p>;
  }

  const passedCount = results ? results.filter((r) => r.passed).length : 0;

  return (
    <div>
      <p className="eyebrow">
        <Link to="/coding">← 코딩 문제 목록</Link>
      </p>

      <section className="hero compact">
        <div className="chip-row">
          <span className={`lang-badge lang-badge--${problem.language.toLowerCase()}`}>
            {LANGUAGE_LABEL[problem.language] ?? problem.language}
          </span>
          <span className="coding-item__difficulty">
            {DIFFICULTY_LABEL[problem.difficulty] ?? problem.difficulty}
          </span>
        </div>
        <h1>{problem.title}</h1>
      </section>

      <div className="card">
        {/* 학습 포인트: white-space: pre-wrap 이면 \n(줄바꿈)이 화면에서도 줄바꿈이 된다.
            그냥 넣으면 HTML은 줄바꿈을 공백 하나로 취급해 문단이 다 붙어 버린다. */}
        <p className="coding-description">{problem.description}</p>
      </div>

      {/* SQL 문제: 테이블 구조와 데이터를 먼저 보여준다 — 데이터를 모르고 쿼리를 쓸 수는 없다 */}
      {isSql && problem.setupSql && (
        <div className="card">
          <details>
            <summary style={{ cursor: 'pointer', fontWeight: 700 }}>
              데이터 살펴보기 (테이블 구조 + 들어 있는 데이터)
            </summary>
            <pre className="sql-setup">{problem.setupSql}</pre>
          </details>
        </div>
      )}

      <div className="card">
        <h2>코드 작성</h2>
        <CodeEditor value={code} onChange={setCode} />

        <div className="coding-actions">
          {(isJs || isSql) && (
            <button type="button" className="btn" onClick={handleRun} disabled={running || submitting}>
              {running ? '실행 중…' : '실행해 보기'}
            </button>
          )}
          <button
            type="button"
            className="btn btn-primary"
            onClick={handleSubmit}
            disabled={submitting || running}
          >
            {submitting ? '제출 중…' : '제출하기'}
          </button>
          <button
            type="button"
            className="btn btn-ghost"
            onClick={() => {
              setCode(problem.starterCode);
              setResults(null);
              setSqlOutput(null);
              setRunError('');
            }}
            disabled={submitting || running}
          >
            처음으로 되돌리기
          </button>
        </div>

        {runError && <p className="error-text">{runError}</p>}
        {message && <p className="success-text">{message}</p>}
        {error && <p className="error-text">{error}</p>}
      </div>

      {/* SQL 문제: 실행 결과 표 vs 기대 결과 표 — 눈으로 비교하며 고칠 수 있게 */}
      {isSql && sqlOutput && (
        <div className="card">
          <h2>
            실행 결과{' '}
            <span className={sqlOutput.passed ? 'ok' : 'ng'}>
              {sqlOutput.passed ? '통과' : '기대 결과와 다름'}
            </span>
          </h2>
          <SqlResultTable columns={sqlOutput.columns} rows={sqlOutput.rows} />
          {!sqlOutput.passed && sqlExpected && (
            <>
              <h3 style={{ marginTop: 16 }}>기대 결과</h3>
              <SqlResultTable columns={sqlExpected.expectedColumns} rows={sqlExpected.expectedRows} />
              {!sqlExpected.orderMatters && (
                <p className="muted" style={{ fontSize: 12.5 }}>
                  이 문제는 행 순서는 채점하지 않아요 — 값이 같으면 통과입니다.
                </p>
              )}
            </>
          )}
        </div>
      )}

      {/* JS 문제: 테스트별 채점 결과.
          SQL 문제도 재방문 시에는 저장된 요약이 이 형식이라 여기로 보여준다
          (다시 [실행]하면 위의 결과 표로 바뀐다). */}
      {(isJs || (isSql && !sqlOutput)) && results && (
        <div className="card">
          <h2>
            채점 결과{' '}
            <span className={passedCount === results.length ? 'ok' : 'ng'}>
              {passedCount}/{results.length} 통과
            </span>
          </h2>
          <ul className="test-results">
            {results.map((result, i) => (
              <li key={i} className={result.passed ? 'test--pass' : 'test--fail'}>
                <span className="test__mark">{result.passed ? '통과' : '실패'}</span>
                <span className="test__name">{result.name}</span>
                {!result.passed && (
                  <span className="test__detail">
                    기대: <code>{result.expected}</code> / 실제: <code>{result.actual}</code>
                  </span>
                )}
              </li>
            ))}
          </ul>
        </div>
      )}

      {/* 자바 문제: 자동채점이 없다는 걸 분명히 알려 준다 */}
      {problem.language === 'JAVA' && (
        <div className="card">
          <h2>채점 안내</h2>
          <p>
            자바 문제는 자동으로 실행하지 않습니다. IntelliJ에서 직접 돌려 확인한 뒤 제출하면,
            멘토가 코드를 읽고 피드백을 남깁니다.
          </p>
        </div>
      )}

      {/* 멘토 피드백 */}
      {problem.feedback && (
        <div className="card feedback-card">
          <h2>멘토 피드백</h2>
          <p className="coding-description">{problem.feedback}</p>
        </div>
      )}
    </div>
  );
}
