package kr.itn.itnhub.code;

import kr.itn.itnhub.AbstractDbTest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * 시드가 설계서대로 들어갔는지 확인한다. 코드값이 데이터가 된 이상 오타 하나가 화면 선택지를
 * 통째로 날리므로, 그룹 간 참조가 맞물리는지까지 여기서 잡는다.
 */
class CodeSeedTest extends AbstractDbTest {

    @Autowired
    JdbcTemplate jdbc;

    @Test
    void 진행단계는_13개이며_13번이_보고서_제출_완료다() {
        List<String> labels = jdbc.queryForList(
                "select label from code where group_id = 'STAGE' order by sort_order", String.class);

        assertThat(labels).hasSize(13);
        assertThat(labels.get(0)).isEqualTo("신청");
        assertThat(labels.get(11)).isEqualTo("보고서 작성");
        assertThat(labels.get(12)).isEqualTo("보고서 제출 완료");
    }

    @Test
    void 진행상태는_1번만_신청이고_13번만_완료다() {
        List<String> applied = jdbc.queryForList(
                "select code from code where group_id = 'STAGE' and attrs->>'progress' = 'applied'",
                String.class);
        List<String> done = jdbc.queryForList(
                "select code from code where group_id = 'STAGE' and attrs->>'progress' = 'done'",
                String.class);
        Integer active = jdbc.queryForObject(
                "select count(*) from code where group_id = 'STAGE' and attrs->>'progress' = 'active'",
                Integer.class);

        assertThat(applied).containsExactly("1");
        assertThat(done).containsExactly("13");
        assertThat(active).isEqualTo(11);
    }

    @Test
    void KPI_임계값이_세_개_지정되어_있다() {
        assertThat(kpiStage("docSubmitted")).isEqualTo("2");
        assertThat(kpiStage("reportWriting")).isEqualTo("12");
        // 요구사항 [2]: 보고서가 실제로 올라온 기관만 완료로 센다.
        assertThat(kpiStage("completed")).isEqualTo("13");
    }

    private String kpiStage(String kpi) {
        return jdbc.queryForObject(
                "select code from code where group_id = 'STAGE' and attrs->>'kpiFrom' = ?",
                String.class, kpi);
    }

    @Test
    void 모든_단계가_업무구간에_속한다() {
        Integer orphan = jdbc.queryForObject(
                "select count(*) from code s where s.group_id = 'STAGE' "
                        + "and not exists (select 1 from code g where g.group_id = 'STAGE_GROUP' "
                        + "  and g.code = s.attrs->>'groupKey')",
                Integer.class);

        assertThat(orphan).isZero();
    }

    @Test
    void 공공누리_유형은_사용처별로_다르게_노출된다() {
        // 세 화면의 목록이 7개/5개/6개로 달랐던 이유는 쓰이는 자리가 달라서다.
        assertThat(koglScope("review")).hasSize(7).doesNotContain("유형 확인 필요");
        assertThat(koglScope("process")).hasSize(6).doesNotContain("보류");
    }

    @Test
    void 기존_부착유형에는_조사표에서_온_값도_들어간다() {
        // 운영 DB 실측(2026-07-28): kogl_type에 '유형 확인 필요' 1,858건, '제4유형' 68건이
        // 저장돼 있었다. 코드로 등록해 두지 않으면 상세 화면에서 선택 안 됨으로 보인다.
        assertThat(koglScope("prior")).containsExactly(
                "0유형", "1유형", "2유형", "3유형", "4유형", "유형 확인 필요", "제4유형");
    }

    @Test
    void 조사표에서_온_값은_변호사_판정_선택지가_아니다() {
        Integer leaked = jdbc.queryForObject(
                "select count(*) from code c "
                        + "where c.group_id = 'KOGL_TYPE' "
                        + "  and (c.attrs->>'surveyOnly')::boolean is true "
                        + "  and exists (select 1 from jsonb_array_elements_text(c.attrs->'scopes') s(v) "
                        + "    where s.v in ('review', 'process'))",
                Integer.class);

        assertThat(leaked).isZero();
    }

    private List<String> koglScope(String scope) {
        return jdbc.queryForList(
                "select code from code c where c.group_id = 'KOGL_TYPE' "
                        + "  and exists (select 1 from jsonb_array_elements_text(c.attrs->'scopes') s(v) "
                        + "    where s.v = ?) "
                        + "order by sort_order",
                String.class, scope);
    }

    @Test
    void 세부항목의_허용_처리결과는_실제_처리결과_코드다() {
        Integer orphan = jdbc.queryForObject(
                "select count(*) from code m, "
                        + "  lateral jsonb_array_elements_text(coalesce(m.attrs->'allowed', '[]'::jsonb)) a(v) "
                        + "where m.group_id = 'REVIEW_MINOR' "
                        + "  and not exists (select 1 from code r "
                        + "    where r.group_id = 'REVIEW_RESULT' and r.code = a.v)",
                Integer.class);

        assertThat(orphan).isZero();
    }

    @Test
    void 대분류와_세부의_추천_처리결과도_실제_코드다() {
        Integer orphan = jdbc.queryForObject(
                "select count(*) from code c "
                        + "where c.group_id in ('REVIEW_MAJOR', 'REVIEW_MINOR') "
                        + "  and c.attrs->>'suggest' is not null "
                        + "  and not exists (select 1 from code r "
                        + "    where r.group_id = 'REVIEW_RESULT' and r.code = c.attrs->>'suggest')",
                Integer.class);

        assertThat(orphan).isZero();
    }

    @Test
    void 처리결과가_지정하는_공공누리_유형도_실제_코드다() {
        Integer orphan = jdbc.queryForObject(
                "select count(*) from code c "
                        + "where c.group_id = 'REVIEW_RESULT' "
                        + "  and c.attrs->>'koglSet' is not null "
                        + "  and not exists (select 1 from code k "
                        + "    where k.group_id = 'KOGL_TYPE' and k.code = c.attrs->>'koglSet')",
                Integer.class);

        assertThat(orphan).isZero();
    }

    @Test
    void 열두_그룹이_모두_시드된다() {
        Integer groups = jdbc.queryForObject("select count(*) from code_group", Integer.class);
        Integer empty = jdbc.queryForObject(
                "select count(*) from code_group g "
                        + "where not exists (select 1 from code c where c.group_id = g.group_id)",
                Integer.class);

        assertThat(groups).isEqualTo(12);
        assertThat(empty).isZero();
    }

    /**
     * tone은 화면에 실제로 쓰이는 색과 같아야 한다. 다르면 Phase 2에서 화면 색이 조용히 바뀐다.
     * 기준: Dashboard.tsx의 GROUP_COLOR(칸반 컬럼 색 띠).
     */
    @Test
    void 업무구간_색이_지금_화면과_같다() {
        assertThat(tone("STAGE_GROUP", "intake")).isEqualTo("slate");
        assertThat(tone("STAGE_GROUP", "assign")).isEqualTo("indigo");
        assertThat(tone("STAGE_GROUP", "check")).isEqualTo("emerald");
        assertThat(tone("STAGE_GROUP", "process")).isEqualTo("amber");
        assertThat(tone("STAGE_GROUP", "closing")).isEqualTo("violet");
    }

    /** 기준: ContactsPage.tsx의 CATEGORY_BADGE_STYLES. */
    @Test
    void 담당자구분_색이_지금_화면과_같다() {
        assertThat(tone("CONTACT_CATEGORY", "APPLICANT")).isEqualTo("blue");
        assertThat(tone("CONTACT_CATEGORY", "MJ")).isEqualTo("emerald");
        assertThat(tone("CONTACT_CATEGORY", "LAWYER")).isEqualTo("violet");
        assertThat(tone("CONTACT_CATEGORY", "OPERATOR")).isEqualTo("sky");
        assertThat(tone("CONTACT_CATEGORY", "ITN")).isEqualTo("orange");
    }

    /** 기준: ReviewBoard.tsx의 REVIEW_RESULT_BADGE_STYLES. */
    @Test
    void 처리결과_색이_지금_화면과_같다() {
        assertThat(tone("REVIEW_RESULT", "신유형 개방")).isEqualTo("emerald");
        assertThat(tone("REVIEW_RESULT", "계약서 등 재확인")).isEqualTo("amber");
        assertThat(tone("REVIEW_RESULT", "권리처리 추진")).isEqualTo("blue");
        assertThat(tone("REVIEW_RESULT", "권리처리 추진 미희망")).isEqualTo("slate");
        assertThat(tone("REVIEW_RESULT", "개방불가")).isEqualTo("red");
    }

    /**
     * MJ는 화면에 '문정원'으로 나오지만 담당자 명부 엑셀에는 '주관기관'으로 적혀 온다.
     * 이 둘을 헷갈리면 화면 표시가 바뀌거나 엑셀 업로드가 깨진다.
     */
    @Test
    void 담당자구분은_화면표시와_엑셀표기를_따로_갖는다() {
        assertThat(label("CONTACT_CATEGORY", "MJ")).isEqualTo("문정원");
        assertThat(attr("CONTACT_CATEGORY", "MJ", "importAlias")).isEqualTo("주관기관");
        // 나머지는 화면표시와 엑셀표기가 같다.
        assertThat(label("CONTACT_CATEGORY", "LAWYER")).isEqualTo("변호사");
        assertThat(attr("CONTACT_CATEGORY", "LAWYER", "importAlias")).isEqualTo("변호사");
    }

    private String tone(String groupId, String code) {
        return attr(groupId, code, "tone");
    }

    private String attr(String groupId, String code, String key) {
        return jdbc.queryForObject(
                "select attrs->>? from code where group_id = ? and code = ?",
                String.class, key, groupId, code);
    }

    private String label(String groupId, String code) {
        return jdbc.queryForObject(
                "select label from code where group_id = ? and code = ?",
                String.class, groupId, code);
    }

    @Test
    void 그룹마다_정렬순서가_중복되지_않는다() {
        Integer dup = jdbc.queryForObject(
                "select count(*) from (select group_id, sort_order from code "
                        + " group by group_id, sort_order having count(*) > 1) t",
                Integer.class);

        assertThat(dup).isZero();
    }
}
