import { isNumber, isPlainObject, isString } from "es-toolkit";
import { dirname, resolve } from "node:path";

import type { ConfigValidationIssue } from "./schema/issues";
import type {
  DefaultsConfig,
  ExecutionConfig,
  LoggingConfig,
  LogLevel,
  RawTargetConfig,
  ResolvedLoggingConfig,
  ResolvedTargetBase,
  RotationFrequency,
  ServerStorageConfig,
} from "./types";

import { checkerRegistry } from "./runner";
import { issue, throwConfigIssues } from "./schema/issues";
import { asValidatedConfig, type RawProbeConfig } from "./schema/types";
import { validateProbeConfigContract } from "./schema/validate";
import { parseDuration, parseSize } from "./utils";
import { resolveVariables } from "./variables";

const DEFAULT_HOST = "127.0.0.1";
const DEFAULT_PORT = 3000;
const DEFAULT_DATA_DIR = "./data";
const DEFAULT_INTERVAL = "30s";
const DEFAULT_TIMEOUT = "10s";
const DEFAULT_MAX_CONCURRENT_CHECKS = 20;
const DEFAULT_RETENTION = "7d";
const DEFAULT_LOG_LEVEL: LogLevel = "info";
const DEFAULT_ROTATION_SIZE = "50MB";
const DEFAULT_ROTATION_FREQUENCY: RotationFrequency = "daily";
const DEFAULT_ROTATION_MAX_FILES = 14;

const VALID_LOG_LEVELS: LogLevel[] = ["trace", "debug", "info", "warn", "error", "fatal"];
const VALID_ROTATION_FREQUENCIES: RotationFrequency[] = ["hourly", "daily", "weekly"];

export interface ResolvedConfig {
  configDir: string;
  dataDir: string;
  host: string;
  logging: ResolvedLoggingConfig;
  maxConcurrentChecks: number;
  port: number;
  retentionMs: number;
  targets: ResolvedTargetBase[];
}

export async function loadConfig(configPath: string): Promise<ResolvedConfig> {
  const file = Bun.file(configPath);

  if (!(await file.exists())) {
    throw new Error(`配置文件不存在: ${configPath}`);
  }

  const content = await file.text();
  const parsed = Bun.YAML.parse(content);

  if (!parsed) {
    throw new Error("配置文件内容为空或格式无效");
  }

  const variableResult = resolveVariables(parsed);
  if (variableResult.issues.length > 0) {
    throwConfigIssues(dedupeIssues(variableResult.issues));
  }

  const resolvedVariablesConfig = variableResult.config;
  const contractResult = validateProbeConfigContract(resolvedVariablesConfig, checkerRegistry);
  if (contractResult.config === null && !canRunSemanticValidation(resolvedVariablesConfig)) {
    throwConfigIssues(contractResult.issues);
  }
  const semanticInput = (contractResult.config ?? resolvedVariablesConfig) as RawProbeConfig;
  const validationIssues = validateConfig(semanticInput);

  const allIssues = [...contractResult.issues, ...validationIssues];
  if (contractResult.config === null) {
    if (allIssues.length > 0) {
      throwConfigIssues(dedupeIssues(allIssues));
    }
    throw new Error("配置文件内容为空或格式无效");
  }

  const raw = contractResult.config;

  const validated = asValidatedConfig(raw);

  const configDir = dirname(resolve(configPath));
  const server = validated.server ?? {};
  const listen = server.listen ?? {};
  const storage = server.storage ?? {};
  const defaults = validated.defaults ?? {};

  const host = listen.host ?? DEFAULT_HOST;
  const port = listen.port ?? DEFAULT_PORT;
  const dataDir = resolve(configDir, storage.dataDir ?? DEFAULT_DATA_DIR);

  const probes = validated.probes ?? {};
  const execution = probes.execution ?? {};
  const maxConcurrentChecks = resolveMaxConcurrentChecks(execution);
  const retentionMs = resolveRetention(storage);

  const logging = resolveLogging(server.logging ?? {}, dataDir, configDir);

  const allRuntimeIssues = [...allIssues];
  validateLoggingConfig(server.logging, allRuntimeIssues);
  if (allRuntimeIssues.length > 0) {
    throwConfigIssues(dedupeIssues(allRuntimeIssues));
  }

  const defaultIntervalMs = parseDuration(defaults.interval ?? DEFAULT_INTERVAL);
  const defaultTimeoutMs = parseDuration(defaults.timeout ?? DEFAULT_TIMEOUT);

  const targets: ResolvedTargetBase[] = validated.targets.map((target) =>
    resolveTarget(target, defaults, defaultIntervalMs, defaultTimeoutMs, configDir),
  );

  return { configDir, dataDir, host, logging, maxConcurrentChecks, port, retentionMs, targets };
}

function canRunSemanticValidation(value: unknown): boolean {
  return isPlainObject(value);
}

function dedupeIssues(issues: ConfigValidationIssue[]): ConfigValidationIssue[] {
  const seen = new Set<string>();
  const result: ConfigValidationIssue[] = [];
  for (const item of issues) {
    const key = `${item.code}:${item.path}:${item.message}:${item.targetName ?? ""}:${item.targetId ?? ""}`;
    if (seen.has(key)) continue;
    seen.add(key);
    result.push(item);
  }
  return result;
}

export { parseDuration } from "./utils";

function isAbsolute(p: string): boolean {
  return p.startsWith("/") || /^[A-Za-z]:/.test(p);
}

function resolveLogging(logging: LoggingConfig, dataDir: string, configDir: string): ResolvedLoggingConfig {
  const globalLevel = resolveLogLevel(logging.level, DEFAULT_LOG_LEVEL);
  const consoleLevel = resolveLogLevel(logging.console?.level, globalLevel);
  const fileLevel = resolveLogLevel(logging.file?.level, globalLevel);

  const rawPath = logging.file?.path;
  const filePath = rawPath
    ? isAbsolute(rawPath)
      ? rawPath
      : resolve(configDir, rawPath)
    : resolve(dataDir, "logs/dial.log");

  const rotationRaw = logging.file?.rotation;
  const rotationSizeRaw = rotationRaw?.size ?? DEFAULT_ROTATION_SIZE;
  const rotationSizeBytes = parseSize(rotationSizeRaw);
  const rotationFrequency = rotationRaw?.frequency ?? DEFAULT_ROTATION_FREQUENCY;
  const rotationMaxFiles = rotationRaw?.maxFiles ?? DEFAULT_ROTATION_MAX_FILES;

  return {
    consoleLevel,
    fileLevel,
    filePath,
    rotationFrequency,
    rotationMaxFiles,
    rotationSizeBytes,
    rotationSizeRaw,
  };
}

function resolveLogLevel(level: unknown, fallback: LogLevel): LogLevel {
  if (!isString(level)) return fallback;
  if (VALID_LOG_LEVELS.includes(level as LogLevel)) return level as LogLevel;
  return fallback;
}

function resolveMaxConcurrentChecks(execution: ExecutionConfig): number {
  if (execution.maxConcurrentChecks === undefined) return DEFAULT_MAX_CONCURRENT_CHECKS;
  if (
    !isNumber(execution.maxConcurrentChecks) ||
    !Number.isInteger(execution.maxConcurrentChecks) ||
    execution.maxConcurrentChecks <= 0
  )
    return DEFAULT_MAX_CONCURRENT_CHECKS;
  return execution.maxConcurrentChecks;
}

function resolveRetention(storage: ServerStorageConfig): number {
  return parseDuration(storage.retention ?? DEFAULT_RETENTION);
}

function resolveTarget(
  target: RawTargetConfig,
  defaults: DefaultsConfig,
  defaultIntervalMs: number,
  defaultTimeoutMs: number,
  configDir: string,
): ResolvedTargetBase {
  const intervalMs = parseDuration(target.interval ?? defaults.interval ?? DEFAULT_INTERVAL);
  const timeoutMs = parseDuration(target.timeout ?? defaults.timeout ?? DEFAULT_TIMEOUT);

  const checker = checkerRegistry.get(target.type);
  const result = checker.resolve(target, { configDir, defaultIntervalMs, defaults, defaultTimeoutMs });

  result.intervalMs = intervalMs;
  result.timeoutMs = timeoutMs;
  result.description = target.description ?? null;

  return result;
}

function validateConfig(config: RawProbeConfig): ConfigValidationIssue[] {
  const issues: ConfigValidationIssue[] = [];
  if (!Array.isArray(config.targets) || config.targets.length === 0) {
    issues.push(issue("required", "targets", "配置文件必须包含至少一个 target"));
    return issues;
  }
  const ids = new Set<string>();
  const supportedTypes = checkerRegistry.supportedTypes;

  for (let i = 0; i < config.targets.length; i++) {
    const rawTarget = config.targets[i] as unknown;
    if (!isPlainObject(rawTarget)) {
      issues.push(issue("invalid-type", `targets[${i}]`, "必须为对象"));
      continue;
    }
    const raw = rawTarget as Record<string, unknown>;

    const id: unknown = raw["id"];
    if (!isString(id) || id.trim() === "") {
      issues.push(issue("required", `targets[${i}].id`, "缺少 id 字段"));
      continue;
    }

    if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(id)) {
      issues.push(issue("invalid-format", `targets[${i}].id`, "id 不符合命名规则", id));
    }

    const nameValue: unknown = raw["name"];
    const name = isString(nameValue) ? nameValue : id;

    if (isString(nameValue) && nameValue.trim() === "") {
      issues.push(issue("invalid-value", `targets[${i}].name`, "name 不能为空白", name));
    }

    const type: unknown = raw["type"];
    if (!isString(type)) {
      issues.push(issue("required", `targets[${i}].type`, "缺少 type 字段", name));
      continue;
    }

    if (!supportedTypes.includes(type)) {
      issues.push(
        issue(
          "unsupported-type",
          `targets[${i}].type`,
          `使用不支持的 type: "${type}"，支持: ${supportedTypes.join(", ")}`,
          name,
        ),
      );
    }

    const group: unknown = raw["group"];
    if (group !== undefined && !isString(group)) {
      issues.push(issue("invalid-type", `targets[${i}].group`, "必须为字符串", name));
    }

    if (ids.has(id)) {
      issues.push(issue("duplicate-id", `targets[${i}].id`, `target id 重复: "${id}"`, name));
    }

    ids.add(id);
  }

  for (const checker of checkerRegistry.definitions) {
    issues.push(...checker.validate({ defaults: config.defaults ?? {}, targets: config.targets }));
  }

  validateDurationValue(config.defaults?.interval, "defaults.interval", issues);
  validateDurationValue(config.defaults?.timeout, "defaults.timeout", issues);
  validateDurationValue(
    isString(config.server?.storage?.retention) ? config.server.storage.retention : undefined,
    "server.storage.retention",
    issues,
  );
  for (let i = 0; i < config.targets.length; i++) {
    const target = config.targets[i] as unknown;
    if (!isPlainObject(target)) continue;
    const targetRecord = target as Record<string, unknown>;
    const targetNameValue: unknown = targetRecord["name"];
    const targetIdValue: unknown = targetRecord["id"];
    const targetName = isString(targetNameValue)
      ? targetNameValue
      : isString(targetIdValue)
        ? targetIdValue
        : undefined;
    validateDurationValue(
      isString(targetRecord["interval"]) ? targetRecord["interval"] : undefined,
      `targets[${i}].interval`,
      issues,
      targetName,
    );
    validateDurationValue(
      isString(targetRecord["timeout"]) ? targetRecord["timeout"] : undefined,
      `targets[${i}].timeout`,
      issues,
      targetName,
    );
  }

  return issues;
}

function validateDurationValue(
  value: string | undefined,
  path: string,
  issues: ConfigValidationIssue[],
  targetName?: string,
): void {
  if (value === undefined) return;
  try {
    parseDuration(value);
  } catch (error) {
    issues.push(issue("invalid-duration", path, error instanceof Error ? error.message : "时长格式不合法", targetName));
  }
}

function validateLoggingConfig(logging: LoggingConfig | undefined, issues: ConfigValidationIssue[]): void {
  if (logging === undefined) return;

  if (logging.level !== undefined && !VALID_LOG_LEVELS.includes(logging.level)) {
    issues.push(
      issue(
        "invalid-value",
        "server.logging.level",
        `日志等级非法: "${logging.level}"，支持: ${VALID_LOG_LEVELS.join(", ")}`,
      ),
    );
  }

  if (logging.console?.level !== undefined && !VALID_LOG_LEVELS.includes(logging.console.level)) {
    issues.push(
      issue(
        "invalid-value",
        "server.logging.console.level",
        `日志等级非法: "${logging.console.level}"，支持: ${VALID_LOG_LEVELS.join(", ")}`,
      ),
    );
  }

  if (logging.file?.level !== undefined && !VALID_LOG_LEVELS.includes(logging.file.level)) {
    issues.push(
      issue(
        "invalid-value",
        "server.logging.file.level",
        `日志等级非法: "${logging.file.level}"，支持: ${VALID_LOG_LEVELS.join(", ")}`,
      ),
    );
  }

  if (logging.file?.path !== undefined) {
    if (!isString(logging.file.path) || logging.file.path.trim() === "") {
      issues.push(issue("invalid-value", "server.logging.file.path", "日志路径不能为空字符串或空白字符串"));
    }
  }

  const rotation = logging.file?.rotation;
  if (rotation?.size !== undefined) {
    try {
      const bytes = parseSize(rotation.size);
      if (bytes <= 0) {
        issues.push(issue("invalid-value", "server.logging.file.rotation.size", "滚动大小必须为正整数字节数"));
      }
    } catch (error) {
      issues.push(
        issue(
          "invalid-value",
          "server.logging.file.rotation.size",
          error instanceof Error ? error.message : "size 格式非法",
        ),
      );
    }
  }

  if (rotation?.frequency !== undefined && !VALID_ROTATION_FREQUENCIES.includes(rotation.frequency)) {
    issues.push(
      issue(
        "invalid-value",
        "server.logging.file.rotation.frequency",
        `滚动频率非法: "${rotation.frequency}"，支持: ${VALID_ROTATION_FREQUENCIES.join(", ")}`,
      ),
    );
  }

  if (rotation?.maxFiles !== undefined) {
    if (!isNumber(rotation.maxFiles) || !Number.isInteger(rotation.maxFiles) || rotation.maxFiles <= 0) {
      issues.push(issue("invalid-value", "server.logging.file.rotation.maxFiles", "maxFiles 必须为正整数"));
    }
  }
}
