package com.cloudhandson.vpdbackoffice.domain.probe;

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

import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;

class ProbeResultTest {

  @Test
  void explainsSuccessfulResultInPlainLanguage() {
    ProbeResult result = new ProbeResult(
        ProbeStatus.SUCCESS,
        List.of("DOC_ID"),
        List.of(Map.of("DOC_ID", 1), Map.of("DOC_ID", 2)),
        2,
        List.of(),
        null,
        null,
        null,
        null,
        null,
        null
    );

    assertThat(result.successLike()).isTrue();
    assertThat(result.title()).contains("데이터를 볼 수 있습니다");
    assertThat(result.plainSummary()).contains("2개").contains("행 접근 정책");
    assertThat(result.nextAction()).contains("예상한 범위");
  }

  @Test
  void treatsEmptyRowsAsAnEnforcedPermissionOutcome() {
    ProbeResult result = new ProbeResult(
        ProbeStatus.VPD_DENY_EMPTY_RESULT,
        List.of(),
        List.of(),
        0,
        List.of(),
        null,
        null,
        null,
        null,
        null,
        null
    );

    assertThat(result.successLike()).isTrue();
    assertThat(result.title()).contains("볼 수 있는 행이 없습니다");
    assertThat(result.plainSummary()).contains("오류가 아닐 수 있습니다");
    assertThat(result.nextAction()).contains("유효 권한");
  }

  @Test
  void givesActionableGuidanceForUnknownToken() {
    ProbeResult result = ProbeResult.blocked(
        ProbeStatus.TOKEN_NOT_FOUND,
        "TOKEN_NOT_FOUND",
        "토큰을 찾을 수 없습니다."
    );

    assertThat(result.successLike()).isFalse();
    assertThat(result.title()).contains("등록되지 않은 토큰");
    assertThat(result.plainSummary()).contains("DB").contains("원문");
    assertThat(result.nextAction()).contains("새 토큰").contains("발급");
  }

  @Test
  void distinguishesInactiveTokenAndOrdsFailure() {
    ProbeResult inactive = ProbeResult.blocked(
        ProbeStatus.TOKEN_INACTIVE,
        "TOKEN_INACTIVE",
        "만료되었거나 회수된 토큰입니다."
    );
    ProbeResult unavailable = ProbeResult.blocked(
        ProbeStatus.ORDS_UNAVAILABLE,
        "ORDS_UNAVAILABLE",
        "연결할 수 없습니다."
    );

    assertThat(inactive.title()).contains("만료되었거나 회수");
    assertThat(inactive.nextAction()).contains("활성 토큰");
    assertThat(unavailable.title()).contains("ORDS");
    assertThat(unavailable.nextAction()).contains("행 접근 규칙을 바꾸지 말고");
  }

  @Test
  void identifiesBrokenCustomVpdFilterSeparatelyFromTokenErrors() {
    ProbeResult result = ProbeResult.blocked(
        ProbeStatus.VPD_FILTER_ERROR,
        "VPD_FILTER_ERROR",
        "ORA-28110"
    );

    assertThat(result.title()).contains("행 접근 Filter");
    assertThat(result.plainSummary()).contains("토큰과 사용자 권한은 확인");
    assertThat(result.nextAction()).contains("토큰이나 행 접근 규칙을 바꾸지 말고").contains("표준 행 접근 Filter");
  }

  @Test
  void exposesVpdSqlTraceWithoutChangingTheRows() {
    ProbeResult result = new ProbeResult(
        ProbeStatus.SUCCESS,
        List.of("DEPT_CODE"),
        List.of(Map.of("DEPT_CODE", "HR")),
        1,
        List.of(),
        null,
        null,
        null,
        null,
        null,
        null
    ).withSqlTrace(
        "(DEPT_CODE = SYS_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE'))",
        "SELECT * FROM ADMIN.DOCUMENTS WHERE (DEPT_CODE = SYS_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE'))"
    );

    assertThat(result.hasSqlTrace()).isTrue();
    assertThat(result.vpdPredicate()).contains("DEPT_CODE");
    assertThat(result.effectiveSql()).contains("ADMIN.DOCUMENTS");
    assertThat(result.rowCount()).isEqualTo(1);
  }

  @Test
  void keepsDatabaseExecutionEvidenceSeparateFromTheReconstructedSql() {
    SqlExecutionEvidence evidence = new SqlExecutionEvidence(
        "4f2jz7n2k0s3p",
        1,
        "SELECT d.doc_id FROM ADMIN.CB_V_SEARCH_DOCUMENTS d WHERE ROWNUM <= :1",
        "2026-07-01T10:00:00",
        3,
        12,
        35,
        120,
        "Predicate Information (identified by operation id):\n1 - filter(DEPT_CODE = SYS_CONTEXT(...))"
    );
    ProbeResult result = new ProbeResult(
        ProbeStatus.SUCCESS,
        List.of("DOC_ID"),
        List.of(Map.of("DOC_ID", 1)),
        1,
        List.of(),
        null,
        null,
        null,
        null,
        null,
        null
    ).withSqlTrace("DEPT_CODE = SYS_CONTEXT(...)", "SELECT ... WHERE (...)")
        .withExecutionEvidence(evidence, null);

    assertThat(result.hasExecutionEvidence()).isTrue();
    assertThat(result.executionEvidence().sqlId()).isEqualTo("4f2jz7n2k0s3p");
    assertThat(result.executionEvidence().hasPredicatePlan()).isTrue();
    assertThat(result.effectiveSql()).contains("WHERE");
  }

  @Test
  void keepsRecentCursorCandidatesWhenNoObjectMatchWasFound() {
    ProbeResult result = new ProbeResult(
        ProbeStatus.SUCCESS,
        List.of(),
        List.of(),
        0,
        List.of(),
        null,
        null,
        null,
        null,
        null,
        null
    ).withExecutionEvidence(null, "최근 cursor를 찾지 못했습니다.", List.of(
        new SqlExecutionCandidate(
            "candidate1", 0, "CB_ORDS", "SELECT 1 FROM dual", "2026-07-01T15:00", false)
    ));

    assertThat(result.hasExecutionEvidence()).isFalse();
    assertThat(result.hasExecutionCandidates()).isTrue();
    assertThat(result.executionCandidates()).hasSize(1);
  }

  @Test
  void keepsAuditedExecutionEvidenceSeparateFromTheReconstructedSql() {
    FgaExecutionEvidence evidence = new FgaExecutionEvidence(
        "2026-07-01T10:00:00Z",
        "CB_ORDS",
        "probe-123",
        "SELECT",
        "SELECT d.doc_id FROM ADMIN.CB_V_SEARCH_DOCUMENTS d",
        "CB_AGENT_DOC_POLICY: (DEPT_CODE = 'HR')"
    );
    ProbeResult result = new ProbeResult(
        ProbeStatus.SUCCESS,
        List.of("DOC_ID"),
        List.of(Map.of("DOC_ID", 1)),
        1,
        List.of(),
        null,
        null,
        null,
        null,
        null,
        null
    ).withSqlTrace("DEPT_CODE = SYS_CONTEXT(...)", "SELECT ... WHERE (...)")
        .withFgaExecutionEvidence(evidence, null);

    assertThat(result.hasFgaExecutionEvidence()).isTrue();
    assertThat(result.fgaExecutionEvidence().clientId()).isEqualTo("probe-123");
    assertThat(result.fgaExecutionEvidence().hasRlsInfo()).isTrue();
    assertThat(result.effectiveSql()).contains("WHERE");
  }
}
