import path from "node:path";
import type { OpenClawPluginConfigSchema } from "openclaw/plugin-sdk/core";

export type OpenShellPluginConfig = {
  mode?: string;
  command?: string;
  gateway?: string;
  gatewayEndpoint?: string;
  from?: string;
  policy?: string;
  providers?: string[];
  gpu?: boolean;
  autoProviders?: boolean;
  remoteWorkspaceDir?: string;
  remoteAgentWorkspaceDir?: string;
  timeoutSeconds?: number;
};

export type ResolvedOpenShellPluginConfig = {
  mode: "mirror" | "remote";
  command: string;
  gateway?: string;
  gatewayEndpoint?: string;
  from: string;
  policy?: string;
  providers: string[];
  gpu: boolean;
  autoProviders: boolean;
  remoteWorkspaceDir: string;
  remoteAgentWorkspaceDir: string;
  timeoutMs: number;
};

const DEFAULT_COMMAND = "openshell";
const DEFAULT_MODE = "mirror";
const DEFAULT_SOURCE = "openclaw";
const DEFAULT_REMOTE_WORKSPACE_DIR = "/sandbox";
const DEFAULT_REMOTE_AGENT_WORKSPACE_DIR = "/agent";
const DEFAULT_TIMEOUT_MS = 120_000;

type ParseSuccess = { success: true; data?: OpenShellPluginConfig };
type ParseFailure = {
  success: false;
  error: {
    issues: Array<{ path: Array<string | number>; message: string }>;
  };
};

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function trimString(value: unknown): string | undefined {
  if (typeof value !== "string") {
    return undefined;
  }
  const trimmed = value.trim();
  return trimmed || undefined;
}

function normalizeProviders(value: unknown): string[] | null {
  if (value === undefined) {
    return [];
  }
  if (!Array.isArray(value)) {
    return null;
  }
  const seen = new Set<string>();
  const providers: string[] = [];
  for (const entry of value) {
    if (typeof entry !== "string" || !entry.trim()) {
      return null;
    }
    const normalized = entry.trim();
    if (seen.has(normalized)) {
      continue;
    }
    seen.add(normalized);
    providers.push(normalized);
  }
  return providers;
}

function normalizeRemotePath(value: string | undefined, fallback: string): string {
  const candidate = value ?? fallback;
  const normalized = path.posix.normalize(candidate.trim() || fallback);
  if (!normalized.startsWith("/")) {
    throw new Error(`OpenShell remote path must be absolute: ${candidate}`);
  }
  return normalized;
}

export function createOpenShellPluginConfigSchema(): OpenClawPluginConfigSchema {
  const safeParse = (value: unknown): ParseSuccess | ParseFailure => {
    if (value === undefined) {
      return { success: true, data: undefined };
    }
    if (!isRecord(value)) {
      return {
        success: false,
        error: { issues: [{ path: [], message: "expected config object" }] },
      };
    }
    const allowedKeys = new Set([
      "mode",
      "command",
      "gateway",
      "gatewayEndpoint",
      "from",
      "policy",
      "providers",
      "gpu",
      "autoProviders",
      "remoteWorkspaceDir",
      "remoteAgentWorkspaceDir",
      "timeoutSeconds",
    ]);
    for (const key of Object.keys(value)) {
      if (!allowedKeys.has(key)) {
        return {
          success: false,
          error: { issues: [{ path: [key], message: `unknown config key: ${key}` }] },
        };
      }
    }

    const providers = normalizeProviders(value.providers);
    if (providers === null) {
      return {
        success: false,
        error: {
          issues: [{ path: ["providers"], message: "providers must be an array of strings" }],
        },
      };
    }

    const timeoutSeconds = value.timeoutSeconds;
    if (
      timeoutSeconds !== undefined &&
      (typeof timeoutSeconds !== "number" || !Number.isFinite(timeoutSeconds) || timeoutSeconds < 1)
    ) {
      return {
        success: false,
        error: {
          issues: [{ path: ["timeoutSeconds"], message: "timeoutSeconds must be a number >= 1" }],
        },
      };
    }

    for (const key of ["gpu", "autoProviders"] as const) {
      const candidate = value[key];
      if (candidate !== undefined && typeof candidate !== "boolean") {
        return {
          success: false,
          error: { issues: [{ path: [key], message: `${key} must be a boolean` }] },
        };
      }
    }

    return {
      success: true,
      data: {
        mode: trimString(value.mode),
        command: trimString(value.command),
        gateway: trimString(value.gateway),
        gatewayEndpoint: trimString(value.gatewayEndpoint),
        from: trimString(value.from),
        policy: trimString(value.policy),
        providers,
        gpu: value.gpu as boolean | undefined,
        autoProviders: value.autoProviders as boolean | undefined,
        remoteWorkspaceDir: trimString(value.remoteWorkspaceDir),
        remoteAgentWorkspaceDir: trimString(value.remoteAgentWorkspaceDir),
        timeoutSeconds: timeoutSeconds as number | undefined,
      },
    };
  };

  return {
    safeParse,
    jsonSchema: {
      type: "object",
      additionalProperties: false,
      properties: {
        command: { type: "string" },
        mode: { type: "string", enum: ["mirror", "remote"] },
        gateway: { type: "string" },
        gatewayEndpoint: { type: "string" },
        from: { type: "string" },
        policy: { type: "string" },
        providers: { type: "array", items: { type: "string" } },
        gpu: { type: "boolean" },
        autoProviders: { type: "boolean" },
        remoteWorkspaceDir: { type: "string" },
        remoteAgentWorkspaceDir: { type: "string" },
        timeoutSeconds: { type: "number", minimum: 1 },
      },
    },
  };
}

export function resolveOpenShellPluginConfig(value: unknown): ResolvedOpenShellPluginConfig {
  const parsed = createOpenShellPluginConfigSchema().safeParse?.(value);
  if (!parsed || !parsed.success) {
    const issues = parsed && !parsed.success ? parsed.error?.issues : undefined;
    const message =
      issues?.map((issue: { message: string }) => issue.message).join(", ") || "invalid config";
    throw new Error(`Invalid openshell plugin config: ${message}`);
  }
  const raw = parsed.data ?? {};
  const cfg = (raw ?? {}) as OpenShellPluginConfig;
  const mode = cfg.mode ?? DEFAULT_MODE;
  if (mode !== "mirror" && mode !== "remote") {
    throw new Error(`Invalid openshell plugin config: mode must be one of mirror, remote`);
  }
  return {
    mode,
    command: cfg.command ?? DEFAULT_COMMAND,
    gateway: cfg.gateway,
    gatewayEndpoint: cfg.gatewayEndpoint,
    from: cfg.from ?? DEFAULT_SOURCE,
    policy: cfg.policy,
    providers: cfg.providers ?? [],
    gpu: cfg.gpu ?? false,
    autoProviders: cfg.autoProviders ?? true,
    remoteWorkspaceDir: normalizeRemotePath(cfg.remoteWorkspaceDir, DEFAULT_REMOTE_WORKSPACE_DIR),
    remoteAgentWorkspaceDir: normalizeRemotePath(
      cfg.remoteAgentWorkspaceDir,
      DEFAULT_REMOTE_AGENT_WORKSPACE_DIR,
    ),
    timeoutMs:
      typeof cfg.timeoutSeconds === "number"
        ? Math.floor(cfg.timeoutSeconds * 1000)
        : DEFAULT_TIMEOUT_MS,
  };
}
