package com.cloudhandson.vpdbackoffice.domain.probe;

import java.util.List;
import java.util.Map;

public record ProbeResult(
    ProbeStatus status,
    List<String> columns,
    List<Map<String, Object>> rows,
    int rowCount,
    List<String> maskedColumns,
    String errorCode,
    String errorMessage,
    String requestHeaders,
    String requestPayload,
    String responseHeaders,
    String responseBody,
    String vpdPredicate,
    String effectiveSql,
    SqlExecutionEvidence executionEvidence,
    String executionEvidenceMessage,
    List<SqlExecutionCandidate> executionCandidates,
    FgaExecutionEvidence fgaExecutionEvidence,
    String fgaExecutionEvidenceMessage
) {

  public ProbeResult(
      ProbeStatus status,
      List<String> columns,
      List<Map<String, Object>> rows,
      int rowCount,
      List<String> maskedColumns,
      String errorCode,
      String errorMessage,
      String requestHeaders,
      String requestPayload,
      String responseHeaders,
      String responseBody
  ) {
    this(
        status,
        columns,
        rows,
        rowCount,
        maskedColumns,
        errorCode,
        errorMessage,
        requestHeaders,
        requestPayload,
        responseHeaders,
        responseBody,
        null,
        null,
        null,
        null,
        List.of(),
        null,
        null
    );
  }

  public ProbeResult(
      ProbeStatus status,
      List<String> columns,
      List<Map<String, Object>> rows,
      int rowCount,
      List<String> maskedColumns,
      String errorCode,
      String errorMessage,
      String requestHeaders,
      String requestPayload,
      String responseHeaders,
      String responseBody,
      String vpdPredicate,
      String effectiveSql
  ) {
    this(
        status,
        columns,
        rows,
        rowCount,
        maskedColumns,
        errorCode,
        errorMessage,
        requestHeaders,
        requestPayload,
        responseHeaders,
        responseBody,
        vpdPredicate,
        effectiveSql,
        null,
        null,
        List.of(),
        null,
        null
    );
  }

  public static ProbeResult blocked(ProbeStatus status, String errorCode, String errorMessage) {
    return blocked(status, errorCode, errorMessage, null, null, null, null);
  }

  public static ProbeResult blocked(
      ProbeStatus status,
      String errorCode,
      String errorMessage,
      String requestHeaders,
      String requestPayload,
      String responseHeaders,
      String responseBody
  ) {
    return new ProbeResult(
        status,
        List.of(),
        List.of(),
        0,
        List.of(),
        errorCode,
        errorMessage,
        requestHeaders,
        requestPayload,
        responseHeaders,
        responseBody
    );
  }

  public boolean successLike() {
    return status == ProbeStatus.SUCCESS || status == ProbeStatus.VPD_DENY_EMPTY_RESULT;
  }

  public boolean hasSqlTrace() {
    return effectiveSql != null && !effectiveSql.isBlank();
  }

  public ProbeResult withSqlTrace(String predicate, String sql) {
    return new ProbeResult(
        status,
        columns,
        rows,
        rowCount,
        maskedColumns,
        errorCode,
        errorMessage,
        requestHeaders,
        requestPayload,
        responseHeaders,
        responseBody,
        predicate,
        sql,
        executionEvidence,
        executionEvidenceMessage,
        executionCandidates,
        fgaExecutionEvidence,
        fgaExecutionEvidenceMessage
    );
  }

  public boolean hasExecutionEvidence() {
    return executionEvidence != null && executionEvidence.sqlId() != null
        && !executionEvidence.sqlId().isBlank();
  }

  public ProbeResult withExecutionEvidence(
      SqlExecutionEvidence evidence,
      String unavailableMessage
  ) {
    return withExecutionEvidence(evidence, unavailableMessage, List.of());
  }

  public ProbeResult withExecutionEvidence(
      SqlExecutionEvidence evidence,
      String unavailableMessage,
      List<SqlExecutionCandidate> candidates
  ) {
    return new ProbeResult(
        status,
        columns,
        rows,
        rowCount,
        maskedColumns,
        errorCode,
        errorMessage,
        requestHeaders,
        requestPayload,
        responseHeaders,
        responseBody,
        vpdPredicate,
        effectiveSql,
        evidence,
        unavailableMessage,
        candidates == null ? List.of() : List.copyOf(candidates),
        fgaExecutionEvidence,
        fgaExecutionEvidenceMessage
    );
  }

  public boolean hasExecutionCandidates() {
    return executionCandidates != null && !executionCandidates.isEmpty();
  }

  public boolean hasFgaExecutionEvidence() {
    return fgaExecutionEvidence != null && fgaExecutionEvidence.sqlText() != null
        && !fgaExecutionEvidence.sqlText().isBlank();
  }

  public ProbeResult withFgaExecutionEvidence(
      FgaExecutionEvidence evidence,
      String unavailableMessage
  ) {
    return new ProbeResult(
        status,
        columns,
        rows,
        rowCount,
        maskedColumns,
        errorCode,
        errorMessage,
        requestHeaders,
        requestPayload,
        responseHeaders,
        responseBody,
        vpdPredicate,
        effectiveSql,
        executionEvidence,
        executionEvidenceMessage,
        executionCandidates,
        evidence,
        unavailableMessage
    );
  }

  public String title() {
    return switch (status) {
      case SUCCESS -> "권한에 따라 데이터를 볼 수 있습니다.";
      case VPD_DENY_EMPTY_RESULT -> "현재 권한으로 볼 수 있는 행이 없습니다.";
      case TOKEN_NOT_FOUND -> "DB에 등록되지 않은 토큰입니다.";
      case TOKEN_INACTIVE -> "만료되었거나 회수된 토큰입니다.";
      case INVALID_TOKEN -> "입력한 토큰 정보가 일치하지 않습니다.";
      case OBJECT_DISABLED -> "검증 대상이 비활성 상태입니다.";
      case OBJECT_NOT_ACCESSIBLE -> "현재 권한으로 이 대상에 접근할 수 없습니다.";
      case VPD_FILTER_ERROR -> "이 객체에 연결된 행 접근 Filter가 실행되지 않습니다.";
      case ORDS_PATH_NOT_FOUND -> "검증 대상의 ORDS 경로를 찾지 못했습니다.";
      case ORDS_NOT_CONFIGURED -> "ORDS 연결 주소가 아직 설정되지 않았습니다.";
      case ORDS_UNAVAILABLE -> "ORDS 서버에 연결할 수 없습니다.";
      case ORDS_TIMEOUT -> "ORDS 응답을 기다리다 시간이 초과되었습니다.";
      case INVALID_ORDS_RESPONSE -> "ORDS 응답 형식을 해석할 수 없습니다.";
      case UNKNOWN_ERROR -> "검증 중 예상하지 못한 문제가 발생했습니다.";
    };
  }

  public String plainSummary() {
    return switch (status) {
      case SUCCESS -> "토큰의 사용자와 역할을 기준으로 행 접근 정책이 적용되었고, 허용된 데이터 " + rowCount
          + "개가 반환되었습니다.";
      case VPD_DENY_EMPTY_RESULT -> "호출은 정상 처리됐지만 행 접근 정책이 현재 사용자에게 허용한 행은 0개입니다. 행 접근 규칙과 실제 데이터가 맞지 않으면 정상 결과이며 오류가 아닐 수 있습니다.";
      case TOKEN_NOT_FOUND -> "입력한 원문과 일치하는 등록 기록이 현재 DB에 없습니다. 예전에 발급한 값이거나 다른 환경의 토큰일 수 있습니다.";
      case TOKEN_INACTIVE -> "토큰은 DB에 있지만 만료되었거나 관리자가 회수해 더 이상 사용자 권한을 증명할 수 없습니다.";
      case INVALID_TOKEN -> "화면에서 선택한 정보와 입력한 토큰 원문이 서로 다릅니다.";
      case OBJECT_DISABLED -> "선택한 객체가 검증 대상으로 활성화되어 있지 않아 ORDS 호출을 시작하지 않았습니다.";
      case OBJECT_NOT_ACCESSIBLE -> "토큰은 확인됐지만 ORDS 또는 DB가 이 객체에 대한 접근을 거부했습니다.";
      case VPD_FILTER_ERROR -> "토큰과 사용자 권한은 확인됐지만, 대상 객체의 VPD 함수가 DB 오류를 내어 행 필터를 계산하지 못했습니다.";
      case ORDS_PATH_NOT_FOUND -> "권한 판단 전 단계에서 실제 ORDS 주소와 등록된 객체 경로가 일치하지 않았습니다.";
      case ORDS_NOT_CONFIGURED -> "백오피스가 호출할 ORDS 기준 주소가 없어 권한 검증을 시작하지 못했습니다.";
      case ORDS_UNAVAILABLE -> "권한 판단 전 단계에서 ORDS 서버 또는 네트워크에 연결하지 못했습니다.";
      case ORDS_TIMEOUT -> "ORDS가 제한 시간 안에 응답하지 않아 권한 결과를 확인하지 못했습니다.";
      case INVALID_ORDS_RESPONSE -> "ORDS 호출은 끝났지만 rows/items 배열이 없는 응답이라 권한 결과로 표시하지 못했습니다.";
      case UNKNOWN_ERROR -> "토큰, 권한, ORDS 중 어느 단계의 문제인지 기술 상세를 확인해야 합니다.";
    };
  }

  public String nextAction() {
    return switch (status) {
      case SUCCESS -> "반환된 행과 ASO 마스킹 컬럼이 예상한 범위인지 확인하세요. 행 범위가 다르면 행 접근 규칙을, 컬럼 표시가 다르면 컬럼 마스킹을 조정한 뒤 다시 검증하세요.";
      case VPD_DENY_EMPTY_RESULT -> "유효 권한 화면에서 사용자에게 직접 또는 그룹으로 상속된 역할과 행 규칙을 확인하세요.";
      case TOKEN_NOT_FOUND -> "토큰 화면에서 현재 환경의 사용자에게 새 토큰을 발급하고, 한 번만 표시되는 원문을 복사해 다시 검증하세요.";
      case TOKEN_INACTIVE -> "토큰 화면에서 활성 토큰을 새로 발급한 뒤 다시 검증하세요.";
      case INVALID_TOKEN -> "복사한 원문이 맞는지 확인하고, 원문을 잃었다면 새 토큰을 발급하세요.";
      case OBJECT_DISABLED -> "보호 객체를 활성화하고 권한을 등록한 뒤 다시 검증하세요.";
      case OBJECT_NOT_ACCESSIBLE -> "유효 권한과 ORDS handler의 대상 객체가 같은지 확인하세요.";
      case VPD_FILTER_ERROR -> "토큰이나 행 접근 규칙을 바꾸지 말고 DB 보호 연결에서 이 객체의 Filter를 확인하세요. 일반 권한 객체라면 표준 행 접근 Filter로 복구하세요.";
      case ORDS_PATH_NOT_FOUND -> "보호 객체의 ORDS 경로와 실제 module/template 경로를 맞춘 뒤 다시 실행하세요.";
      case ORDS_NOT_CONFIGURED -> "설정에서 ORDS 기준 주소를 등록한 뒤 백오피스를 재시작하세요.";
      case ORDS_UNAVAILABLE, ORDS_TIMEOUT -> "행 접근 규칙을 바꾸지 말고 먼저 ORDS 실행 상태, 주소와 네트워크를 확인하세요.";
      case INVALID_ORDS_RESPONSE -> "ORDS handler가 rows 또는 items 배열을 반환하는지 확인하세요.";
      case UNKNOWN_ERROR -> "아래 기술 상세의 오류 코드와 응답을 확인한 뒤 해당 단계부터 점검하세요.";
    };
  }
}
