package dev.awesomedev.mirim.domain;

import jakarta.persistence.*;

import java.time.Instant;

/**
 * 이 파일이 하는 일:
 * 학생이 코딩 문제에 대해 제출한 코드 한 건을 담는 JPA 엔티티다.
 * 같은 문제를 다시 풀면 새 행을 만들지 않고 기존 행을 갱신한다(Submission과 같은 방식).
 *
 * 학습 포인트: 왜 (학생, 문제)당 한 행만 둘까?
 * 매 제출을 다 쌓으면 "이 학생이 이 문제를 풀었나?"를 볼 때마다 최신 행을 골라내야 하고,
 * 멘토 화면도 같은 문제가 여러 번 나와 지저분해진다. 지금 필요한 건 "현재 상태"이지 이력이 아니다.
 * 훗날 "몇 번 만에 통과했나"가 궁금해지면 시도 횟수(attempts)만 세면 되고,
 * 코드 이력 전체가 필요해지면 그때 별도 이력 테이블을 만들면 된다.
 */
@Entity
@Table(name = "coding_submission",
        uniqueConstraints = @UniqueConstraint(columnNames = {"problem_id", "user_id"}))
public class CodingSubmission {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /** 어떤 문제에 대한 제출인지. DB에는 problem_id 외래키 컬럼이 된다. */
    @ManyToOne(optional = false)
    @JoinColumn(name = "problem_id")
    private CodingProblem problem;

    /** 제출한 학생. DB에는 user_id 외래키 컬럼이 된다. */
    @ManyToOne(optional = false)
    @JoinColumn(name = "user_id")
    private User user;

    /** 학생이 작성한 코드 원문 — 멘토가 그대로 읽는다 */
    @Column(nullable = false, columnDefinition = "text")
    private String code;

    /**
     * 상태: SUBMITTED(제출됨) | PASSED(자동채점 전부 통과) | REVIEWED(멘토 피드백 완료)
     * JAVA 문제는 자동채점이 없으므로 SUBMITTED → REVIEWED로만 움직인다.
     */
    @Column(nullable = false, length = 12)
    private String status;

    /** 통과한 테스트 수 (JAVA 문제는 null) */
    @Column(name = "passed_count")
    private Integer passedCount;

    /** 전체 테스트 수 (JAVA 문제는 null) */
    @Column(name = "total_count")
    private Integer totalCount;

    /**
     * 테스트별 채점 결과를 JSON 문자열로 담는다 (JS 전용).
     * 형식: [{"name":"1+2=3","passed":true,"expected":"3","actual":"3"}, ...]
     * 멘토가 "어디서 틀렸는지"를 볼 수 있게 하기 위한 것이다.
     */
    @Column(name = "results_json", columnDefinition = "text")
    private String resultsJson;

    /** 시도 횟수 — 제출할 때마다 1씩 는다 */
    @Column(nullable = false)
    private Integer attempts;

    /** 제출(또는 재제출) 시각 */
    @Column(name = "submitted_at", nullable = false)
    private Instant submittedAt;

    /** 멘토의 피드백. 아직 리뷰 전이면 null. */
    @Column(columnDefinition = "text")
    private String feedback;

    protected CodingSubmission() {
    }

    public CodingSubmission(CodingProblem problem, User user, String code,
                            Integer passedCount, Integer totalCount, String resultsJson) {
        this.problem = problem;
        this.user = user;
        this.attempts = 0;
        submit(code, passedCount, totalCount, resultsJson);
    }

    /**
     * 제출(또는 재제출): 코드와 채점 결과를 갱신하고 시도 횟수를 올린다.
     *
     * 학습 포인트: 상태를 바깥에서 set으로 바꾸지 않고 이 메서드 안에서만 정한다.
     * "전부 통과했으면 PASSED"라는 규칙이 엔티티 안에 한 번만 쓰이므로,
     * 규칙이 바뀌어도 고칠 곳이 한 곳뿐이다. (피드백은 이전 것을 지운다 — 코드가 바뀌었으니까.)
     */
    public void submit(String code, Integer passedCount, Integer totalCount, String resultsJson) {
        this.code = code;
        this.passedCount = passedCount;
        this.totalCount = totalCount;
        this.resultsJson = resultsJson;
        this.attempts = this.attempts + 1;
        this.submittedAt = Instant.now();
        this.feedback = null;
        boolean allPassed = totalCount != null && totalCount > 0 && totalCount.equals(passedCount);
        this.status = allPassed ? "PASSED" : "SUBMITTED";
    }

    /** 멘토 피드백 등록: 피드백을 저장하고 상태를 REVIEWED로 바꾼다. */
    public void review(String feedback) {
        this.feedback = feedback;
        this.status = "REVIEWED";
    }

    public Long getId() {
        return id;
    }

    public CodingProblem getProblem() {
        return problem;
    }

    public User getUser() {
        return user;
    }

    public String getCode() {
        return code;
    }

    public String getStatus() {
        return status;
    }

    public Integer getPassedCount() {
        return passedCount;
    }

    public Integer getTotalCount() {
        return totalCount;
    }

    public String getResultsJson() {
        return resultsJson;
    }

    public Integer getAttempts() {
        return attempts;
    }

    public Instant getSubmittedAt() {
        return submittedAt;
    }

    public String getFeedback() {
        return feedback;
    }
}
