package com.cloudhandson.ddsbackoffice.config;

import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;

@ConfigurationProperties(prefix = "dds")
public record DdsProperties(
    String dbUrl,
    Duration queryTimeout,
    String pgObject,
    String myObject,
    String vectorObject,
    Map<String, User> users,
    Map<String, String> objectMappings,
    Token token
) {

  private static final Pattern OBJECT_NAME = Pattern.compile(
      "[A-Za-z][A-Za-z0-9_$#]*(\\.[A-Za-z][A-Za-z0-9_$#]*)*"
  );

  @ConstructorBinding
  public DdsProperties {
    dbUrl = dbUrl == null ? "" : dbUrl.trim();
    queryTimeout = queryTimeout == null ? Duration.ofSeconds(10) : queryTimeout;
    pgObject = normalizeObject(pgObject, "ADMIN.V_DDS_CUSTOMERS_PG");
    myObject = normalizeObject(myObject, "ADMIN.V_DDS_CUSTOMERS_MY");
    vectorObject = normalizeObject(vectorObject, "ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS");
    var normalizedUsers = new LinkedHashMap<String, User>();
    if (users != null) {
      users.forEach((key, value) -> {
        if (key != null && value != null) {
          normalizedUsers.put(key.trim().toLowerCase(), value);
        }
      });
    }
    users = Map.copyOf(normalizedUsers);
    var normalizedMappings = new LinkedHashMap<String, String>();
    if (objectMappings != null) {
      objectMappings.forEach((key, value) -> {
        if (key != null && value != null && !key.isBlank() && !value.isBlank()) {
          normalizedMappings.put(key.trim().toUpperCase(), normalizeObject(value, ""));
        }
      });
    }
    objectMappings = Map.copyOf(normalizedMappings);
    token = token == null ? new Token("dds_demo_token", "") : token;
  }

  /**
   * Compatibility constructor for small unit tests and older local configs.
   * The DDS vector view has a safe default and can still be overridden by
   * {@code DDS_BACKOFFICE_VECTOR_OBJECT}.
   */
  public DdsProperties(
      String dbUrl,
      Duration queryTimeout,
      String pgObject,
      String myObject,
      Map<String, User> users
  ) {
    this(dbUrl, queryTimeout, pgObject, myObject,
        "ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS", users, Map.of(
            "CB_VECTOR_SEARCH_DOCUMENTS", "ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS"),
        new Token("dds_demo_token", ""));
  }

  public DdsProperties(
      String dbUrl,
      Duration queryTimeout,
      String pgObject,
      String myObject,
      String vectorObject,
      Map<String, User> users
  ) {
    this(dbUrl, queryTimeout, pgObject, myObject, vectorObject, users, Map.of(),
        new Token("dds_demo_token", ""));
  }

  public record Token(String username, String password, String dataRole) {

    @ConstructorBinding
    public Token {
      username = username == null ? "" : username.trim();
      password = password == null ? "" : password;
      dataRole = dataRole == null || dataRole.isBlank() ? "cb_dds_token_role" : dataRole.trim();
    }

    public Token(String username, String password) {
      this(username, password, "cb_dds_token_role");
    }

    public boolean configured() {
      return !username.isBlank() && !password.isBlank();
    }
  }

  public String objectFor(String sourceKey) {
    if ("PG".equalsIgnoreCase(sourceKey)) {
      return pgObject;
    }
    if ("MY".equalsIgnoreCase(sourceKey)) {
      return myObject;
    }
    throw new IllegalArgumentException("지원하지 않는 DDS 데이터 소스입니다.");
  }

  private static String normalizeObject(String value, String fallback) {
    var candidate = value == null || value.isBlank() ? fallback : value.trim();
    if (!OBJECT_NAME.matcher(candidate).matches()) {
      throw new IllegalArgumentException("DDS 보호 객체 이름은 스키마.객체 형식이어야 합니다.");
    }
    return candidate.toUpperCase();
  }

  public record User(
      String label,
      String description,
      String username,
      String password,
      long applicationUserId,
      String dataRole
  ) {

    /**
     * Keep the six-field constructor as the configuration binding target.
     * The four-field overload below exists only for the small query-service
     * tests and older local callers; without an explicit binding marker,
     * Spring Boot can select that overload and silently leave the application
     * user/data-role mapping at its default values.
     */
    @ConstructorBinding
    public User {
    }

    public User(String label, String description, String username, String password) {
      this(label, description, username, password, 0L, "");
    }

    public boolean configured() {
      return username != null && !username.isBlank()
          && password != null && !password.isBlank();
    }

    public boolean mapped() {
      return applicationUserId > 0 && dataRole != null && !dataRole.isBlank();
    }
  }
}
