from fastapi import FastAPI, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
import subprocess
import os
import tempfile
import uuid
from pylint.lint import Run
from pylint.reporters.json_reporter import JSONReporter
from io import StringIO
import sys
import json
from datetime import datetime
from fastapi.responses import RedirectResponse
from typing import List, Optional

app = FastAPI(title="자동 코드 평가 시스템")

# CORS 설정
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 데이터 모델
class CodeSubmission(BaseModel):
    user_id: str
    assignment_id: str
    code: str

class EvaluationResult(BaseModel):
    submission_id: str
    timestamp: str
    test_results: dict
    style_analysis: dict
    feedback: str
    score: float

class Tutorial(BaseModel):
    id: str
    title: str
    description: str
    code_template: str
    solution: Optional[str] = None # 정답 코드 (필요시)

class TutorialSubmission(BaseModel):
    tutorial_id: str
    code: str

class TutorialFeedback(BaseModel):
    passed: bool
    message: str
    output: Optional[str] = None

# 가상의 데이터베이스 (사용자별, 과제별 제출 기록 저장)
# { "user_id": { "assignment_id": [EvaluationResult, ...] } }
submissions_db = {}
test_cases = {
    "assignment1": [
        {"input": "5", "expected_output": "25"},
        {"input": "0", "expected_output": "0"},
        {"input": "10", "expected_output": "100"}
    ],
    "assignment2": [
        {"input": "hello world", "expected_output": "HELLO WORLD"},
        {"input": "python", "expected_output": "PYTHON"}
    ]
}

# 새로운 튜토리얼 데이터
tutorials_db = {
    "1": {
        "id": "1",
        "title": "파이썬 변수와 기본 출력",
        "description": "'name' 변수에 자신의 이름을 할당하고, print() 함수를 사용하여 'Hello, [이름]' 형식으로 콘솔에 출력하는 코드를 작성해보세요.",
        "code_template": "# 'name' 변수에 자신의 이름을 문자열로 할당하세요.\nname = \"\"\n\n# print() 함수를 사용하여 'Hello, '와 name 변수를 함께 출력하세요.\n# 예시: print(\"Hello, \", name)\n"
    },
    "2": {
        "id": "2",
        "title": "기본 연산",
        "description": "두 숫자 변수 a와 b를 더한 결과를 result 변수에 저장하고 출력하세요.",
        "code_template": "a = 10\nb = 5\n\n# a와 b를 더한 결과를 'result'에 저장하세요.\nresult = 0\n\n# 결과를 출력하세요.\n"
    }
}

# 코드 실행 함수
def run_code(code, input_data):
    # 임시 파일 생성
    with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_file:
        temp_file_path = temp_file.name
        temp_file.write(code.encode())
    
    try:
        # 안전한 환경에서 코드 실행 (실제로는 Docker 등 사용)
        process = subprocess.run(
            [sys.executable, temp_file_path],
            input=input_data,
            capture_output=True,
            text=True,
            timeout=5  # 5초 타임아웃
        )
        return process.stdout.strip(), process.returncode
    except subprocess.TimeoutExpired:
        return "실행 시간 초과", 1
    except Exception as e:
        return f"오류: {str(e)}", 1
    finally:
        os.unlink(temp_file_path)

# 코드 스타일 분석
def analyze_code_style(code: str) -> dict:
    """pylint를 사용하여 코드 스타일을 분석합니다."""
    # 임시 파일 생성
    with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.py') as temp_file:
        temp_file_path = temp_file.name
        temp_file.write(code)

    pylint_output = StringIO()
    reporter = JSONReporter(pylint_output)

    try:
        # Pylint 실행
        results = Run([temp_file_path], reporter=reporter, exit=False)
        score = results.linter.stats.global_note

        # JSON 출력 파싱
        pylint_output.seek(0)
        try:
            issues_data = json.load(pylint_output)
            issues = [
                {
                    "line": item.get('line'),
                    "column": item.get('column'),
                    "message": f"({item.get('symbol')}) {item.get('message')}"
                }
                for item in issues_data
            ]
        except json.JSONDecodeError:
            issues = []

        return {
            "score": score if score is not None else 0.0,
            "issues": issues
        }
    except Exception as e:
        print(f"Pylint 오류: {e}")
        return {
            "score": 0.0,
            "issues": [{"message": f"Pylint 실행 중 오류 발생: {e}"}]
        }
    finally:
        os.unlink(temp_file_path)

# 개인화된 피드백 생성
def generate_personalized_suggestions(user_id: str, assignment_id: str, current_style_issues: list) -> str:
    """사용자의 이전 제출 기록을 분석하여 맞춤형 제안을 생성합니다."""
    if user_id not in submissions_db or assignment_id not in submissions_db[user_id]:
        return ""

    history = submissions_db[user_id][assignment_id]
    if len(history) < 2: # 분석할 과거 데이터가 충분하지 않음
        return ""

    # 과거의 스타일 이슈 집계
    past_style_issues = {}
    for past_submission in history:
        for issue in past_submission.style_analysis['issues']:
            # 'C0301' 같은 심볼 추출
            try:
                issue_symbol = issue['message'].split(')')[0][1:]
                past_style_issues[issue_symbol] = past_style_issues.get(issue_symbol, 0) + 1
            except IndexError:
                continue # 포맷이 다른 메시지는 건너뛰기

    # 현재 제출에서도 반복되는 이슈 확인
    recurring_issues_feedback = []
    for issue in current_style_issues:
        try:
            current_symbol = issue['message'].split(')')[0][1:]
            # 과거에 2번 이상 발생했고, 현재도 발생한 경우
            if past_style_issues.get(current_symbol, 0) >= 2:
                feedback = f"- `{current_symbol}` 문제를 계속해서 보이고 있습니다. 관련 규칙을 다시 한번 확인해보세요."
                if feedback not in recurring_issues_feedback:
                    recurring_issues_feedback.append(feedback)
        except IndexError:
            continue
            
    if not recurring_issues_feedback:
        return ""

    suggestions = ["\n💡 맞춤형 제안 (Personalized Tips)"]
    suggestions.extend(recurring_issues_feedback)
    return "\n".join(suggestions)

# 엔드포인트: 코드 제출
@app.post("/submit", response_model=EvaluationResult)
async def submit_code(submission: CodeSubmission):
    # 고유 ID 생성
    submission_id = str(uuid.uuid4())
    timestamp = datetime.now().isoformat()
    
    # 테스트 케이스 실행
    test_results = {}
    if submission.assignment_id in test_cases:
        for i, test_case in enumerate(test_cases[submission.assignment_id]):
            output, returncode = run_code(submission.code, test_case["input"])
            test_results[f"test_{i+1}"] = {
                "input": test_case["input"],
                "expected": test_case["expected_output"],
                "actual": output,
                "passed": output == test_case["expected_output"] and returncode == 0
            }
    
    # 코드 스타일 분석
    style_analysis = analyze_code_style(submission.code)
    
    # --- 피드백 생성 (개선된 로직) ---
    passed_tests = sum(1 for test in test_results.values() if test["passed"])
    total_tests = len(test_results)
    
    # 종합 점수 계산 (테스트 70%, 스타일 30%)
    test_score_raw = (passed_tests / total_tests if total_tests > 0 else 0)
    style_score_raw = (style_analysis["score"] / 10)
    total_score = (test_score_raw * 7) + (style_score_raw * 3)
    
    feedback_parts = []
    
    # 1. 전체 요약
    if passed_tests == total_tests:
        feedback_parts.append("🎉 축하합니다! 모든 기능 테스트를 통과했습니다!")
    else:
        feedback_parts.append("🤔 아쉽지만 일부 기능 테스트를 통과하지 못했습니다. 아래 피드백을 확인하고 코드를 수정해보세요!")
    
    feedback_parts.append("\n" + "="*20)

    # 2. 기능 점수 피드백
    feedback_parts.append("\n✅ 기능 점수 (Test Cases)")
    feedback_parts.append(f"- 전체 테스트: {passed_tests}/{total_tests} 통과")
    if passed_tests < total_tests:
        feedback_parts.append("- 실패한 항목:")
        for i, (name, result) in enumerate(test_results.items()):
            if not result["passed"]:
                feedback_parts.append(f"  - {name}:")
                feedback_parts.append(f"    - 입력: \"{result['input']}\"")
                feedback_parts.append(f"    - 기대값: \"{result['expected']}\"")
                feedback_parts.append(f"    - 실제 출력: \"{result['actual']}\"")

    # 3. 코드 스타일 피드백
    feedback_parts.append("\n🎨 코드 스타일 (Code Style)")
    feedback_parts.append(f"- 스타일 점수: {style_analysis['score']:.1f}/10.0")
    if len(style_analysis["issues"]) > 0:
        feedback_parts.append("- 주요 개선점:")
        for issue in style_analysis["issues"][:5]: # 최대 5개까지 표시
            feedback_parts.append(f"  - {issue['line']}번 줄: {issue['message']}")
    else:
        feedback_parts.append("- 훌륭한 코드 스타일입니다!")

    feedback_parts.append("\n" + "="*20)

    # 맞춤형 제안 추가
    personalized_feedback = generate_personalized_suggestions(
        submission.user_id, submission.assignment_id, style_analysis["issues"]
    )
    if personalized_feedback:
        feedback_parts.append(personalized_feedback)

    # 4. 최종 점수
    feedback_parts.append(f"\n✨ 종합 점수: {total_score:.1f}/10.0")
    
    feedback = "\n".join(feedback_parts)
    # --- 피드백 생성 종료 ---

    # 결과 저장
    result = EvaluationResult(
        submission_id=submission_id,
        timestamp=timestamp,
        test_results=test_results,
        style_analysis=style_analysis,
        feedback=feedback,
        score=total_score
    )
    # 사용자/과제별로 결과 저장
    user_submissions = submissions_db.setdefault(submission.user_id, {})
    assignment_submissions = user_submissions.setdefault(submission.assignment_id, [])
    assignment_submissions.append(result)
    
    return result

# 엔드포인트: 결과 조회
@app.get("/result/{submission_id}", response_model=EvaluationResult)
async def get_result(submission_id: str):
    if submission_id not in submissions_db:
        raise HTTPException(status_code=404, detail="제출 기록을 찾을 수 없습니다")
    return submissions_db[submission_id]

# 정적 파일 마운트
app.mount("/static", StaticFiles(directory="static"), name="static")

@app.get("/")
async def redirect_to_static():
    return RedirectResponse("/static/index.html")

@app.get("/tutorials", response_model=List[Tutorial])
async def get_tutorials():
    """모든 튜토리얼의 목록을 반환합니다."""
    return list(tutorials_db.values())

@app.get("/tutorial/{tutorial_id}", response_model=Tutorial)
async def get_tutorial(tutorial_id: str):
    """특정 튜토리얼의 상세 정보를 반환합니다."""
    if tutorial_id not in tutorials_db:
        raise HTTPException(status_code=404, detail="튜토리얼을 찾을 수 없습니다.")
    return tutorials_db[tutorial_id]

@app.post("/tutorial/submit", response_model=TutorialFeedback)
async def submit_tutorial(submission: TutorialSubmission):
    """튜토리얼 코드를 제출받아 평가하고 피드백을 반환합니다."""
    if submission.tutorial_id not in tutorials_db:
        raise HTTPException(status_code=404, detail="튜토리얼을 찾을 수 없습니다.")

    # 튜토리얼별 평가 로직
    if submission.tutorial_id == "1":
        output, returncode = run_code(submission.code, "")
        if "Hello" in output and returncode == 0:
            return TutorialFeedback(passed=True, message="정답입니다! 'Hello'가 포함된 출력을 확인했습니다.", output=output)
        else:
            return TutorialFeedback(passed=False, message="오답입니다. 'Hello, [이름]' 형식으로 출력되었는지 확인해주세요.", output=output)
    
    elif submission.tutorial_id == "2":
        output, returncode = run_code(submission.code, "")
        # 간단한 검증: '15'가 출력에 포함되었는지 확인
        if "15" in output and returncode == 0:
             return TutorialFeedback(passed=True, message="정답입니다! 덧셈 결과가 정확합니다.", output=output)
        else:
             return TutorialFeedback(passed=False, message="오답입니다. a와 b를 더한 결과가 15인지 확인해주세요.", output=output)

    return TutorialFeedback(passed=False, message="해당 튜토리얼에 대한 평가 로직이 구현되지 않았습니다.")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8001) 