package com.cloudhandson.vpdbackoffice.service;

import com.cloudhandson.vpdbackoffice.config.McpProperties;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.stereotype.Service;

/** Loads MCP tool contracts while retaining the #722 single Select AI compatibility fields. */
@Service
public class EnvironmentMcpToolCatalog implements McpToolCatalog {

  private static final Pattern TOOL_NAME =
      Pattern.compile("[A-Za-z0-9][A-Za-z0-9._-]{0,127}");
  private static final Pattern ARGUMENT_NAME =
      Pattern.compile("[A-Za-z][A-Za-z0-9_]{0,63}");
  private static final Pattern ORACLE_NAME =
      Pattern.compile("[A-Z][A-Z0-9_$#]{0,127}");

  private final List<McpToolDefinition> tools;

  public EnvironmentMcpToolCatalog(
      McpProperties properties,
      ObjectMapper objectMapper
  ) {
    tools = parse(properties, objectMapper);
  }

  @Override
  public List<McpToolDefinition> tools() {
    return tools;
  }

  @Override
  public McpToolDefinition require(String name) {
    return tools.stream()
        .filter(tool -> tool.name().equals(name))
        .findFirst()
        .orElseThrow(() -> new AppException("등록되지 않은 MCP tool입니다: " + name));
  }

  private List<McpToolDefinition> parse(
      McpProperties properties,
      ObjectMapper objectMapper
  ) {
    String raw = properties.tools();
    if (raw == null || raw.isBlank()) {
      return List.of(new McpToolDefinition(
          properties.resolvedToolName(),
          properties.resolvedToolLabel(),
          properties.resolvedToolDescription(),
          "prompt",
          properties.resolvedPromptDescription(),
          "SELECT_AI",
          "",
          ""
      ));
    }
    try {
      List<McpToolDefinition> parsed =
          objectMapper.readValue(raw, new TypeReference<>() {});
      if (parsed.isEmpty()) {
        throw new IllegalArgumentException("MCP 도구 목록이 비어 있습니다.");
      }
      Set<String> names = new HashSet<>();
      List<McpToolDefinition> normalized = parsed.stream()
          .map(this::normalize)
          .peek(tool -> {
            if (!names.add(tool.name())) {
              throw new IllegalArgumentException("중복 MCP tool name: " + tool.name());
            }
          })
          .toList();
      return List.copyOf(normalized);
    } catch (Exception exception) {
      throw new IllegalStateException("BACKOFFICE_MCP_TOOLS 설정을 확인하세요.", exception);
    }
  }

  private McpToolDefinition normalize(McpToolDefinition value) {
    if (value == null) {
      throw new IllegalArgumentException("null MCP tool");
    }
    String name = required(value.name(), "name");
    String argumentName = required(value.argumentName(), "argumentName");
    if (!TOOL_NAME.matcher(name).matches()) {
      throw new IllegalArgumentException("잘못된 MCP tool name");
    }
    if (!ARGUMENT_NAME.matcher(argumentName).matches()) {
      throw new IllegalArgumentException("잘못된 MCP argumentName");
    }
    String executionType = required(value.executionType(), "executionType")
        .toUpperCase(Locale.ROOT);
    if (!Set.of("AGENT_TOOL", "SELECT_AI").contains(executionType)) {
      throw new IllegalArgumentException("잘못된 MCP executionType");
    }
    String targetName = value.targetName() == null ? "" : value.targetName().trim();
    String targetParameterName =
        value.targetParameterName() == null ? "" : value.targetParameterName().trim();
    if ("AGENT_TOOL".equals(executionType)) {
      targetName = oracleName(targetName, "targetName");
      targetParameterName = oracleName(targetParameterName, "targetParameterName");
    } else if (!targetName.isBlank() || !targetParameterName.isBlank()) {
      throw new IllegalArgumentException("SELECT_AI에는 targetName을 지정할 수 없습니다.");
    }
    return new McpToolDefinition(
        name,
        required(value.label(), "label"),
        required(value.description(), "description"),
        argumentName,
        required(value.argumentDescription(), "argumentDescription"),
        executionType,
        targetName,
        targetParameterName
    );
  }

  private String oracleName(String value, String field) {
    String normalized = required(value, field).toUpperCase(Locale.ROOT);
    if (!ORACLE_NAME.matcher(normalized).matches()) {
      throw new IllegalArgumentException("잘못된 " + field);
    }
    return normalized;
  }

  private String required(String value, String field) {
    if (value == null || value.isBlank()) {
      throw new IllegalArgumentException(field + " 값은 필수입니다.");
    }
    return value.trim();
  }
}
