const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { stripBom, sha256File, readJsonFile } = require("./plugin-fs-util");
const { codexHomeForTarget, openCodeConfigPathsForTarget, openCodeProjectRootForTarget, registryPathForScope, defaultPayloadRoot, collectOpenCodePluginEntries } = require("./plugin-agent-paths");

const { marketplaceId, marketplacePluginRef } = require("./plugin-builder");
const { readPluginRecord } = require("./plugin-installer");

function inspectPlugin(pluginName, targets, options = {}) {
  const scope = targets[0] ? targets[0].scope : options.scope || "user";
  const cwd = options.cwd || process.cwd();
  const homeDir = options.homeDir || os.homedir();
  const registry = inspectRegistry(pluginName, { scope, cwd, homeDir });
  const targetReports = targets.map((target) => inspectTarget(pluginName, target));
  const findings = buildFindings(pluginName, registry, targetReports, options);
  if (options.allScopes) {
    findings.push(...crossScopeFindings(pluginName, scope, { cwd, homeDir }));
  }
  return {
    pluginName,
    scope,
    registry,
    targets: targetReports,
    findings,
  };
}

function inspectRegistry(pluginName, options) {
  const registryPath = registryPathForScope(options);
  if (!fs.existsSync(registryPath)) {
    return { path: registryPath, exists: false, hasPlugin: false };
  }
  const parsed = readJsonFile(registryPath, null);
  if (!parsed) {
    return { path: registryPath, exists: true, readable: false, hasPlugin: false };
  }
  return {
    path: registryPath,
    exists: true,
    readable: true,
    schemaVersion: parsed.schemaVersion || null,
    hasPlugin: !!(parsed.plugins && parsed.plugins[pluginName]),
    plugin: parsed.plugins && parsed.plugins[pluginName] ? parsed.plugins[pluginName] : null,
  };
}

function inspectTarget(pluginName, target) {
  const record = readPluginRecord(target.dir, pluginName, target.agent);
  const payloadRoot = record && record.payloadRoot ? record.payloadRoot : defaultPayloadRoot(target, pluginName);
  const pluginRoot = path.dirname(payloadRoot);
  const payloadExists = fs.existsSync(payloadRoot);
  const skillNames = pluginSkillNames(payloadRoot, record);
  const fileValidation = record && Array.isArray(record.files)
    ? validateRecordFiles(record.files)
    : { matched: 0, missing: 0, mismatched: 0 };
  return {
    agent: target.agent,
    scope: target.scope,
    targetDir: target.dir,
    payloadRoot,
    pluginRoot,
    legacyCopy: !!record || payloadExists,
    payloadExists,
    record: record ? summarizeRecord(record) : null,
    fileValidation,
    host: inspectHost(pluginName, target, payloadRoot),
    independentSkills: inspectIndependentSkills(target.dir, skillNames),
  };
}

function summarizeRecord(record) {
  return {
    version: record.version || null,
    contentHash: record.contentHash || null,
    generatedBy: record.generatedBy || null,
    files: Array.isArray(record.files) ? record.files.length : 0,
  };
}

function inspectHost(pluginName, target, payloadRoot) {
  if (target.agent === "codex") {
    return inspectCodexHost(pluginName, target, payloadRoot);
  }
  if (target.agent === "claude") {
    return inspectClaudeHost(pluginName, payloadRoot);
  }
  if (target.agent === "opencode") {
    return inspectOpenCodeHost(target, payloadRoot);
  }
  return { agent: target.agent, supported: false };
}

function inspectCodexHost(pluginName, target, payloadRoot) {
  const plugin = { name: pluginName };
  const codexHome = codexHomeForTarget(target);
  const configPath = path.join(codexHome, "config.toml");
  const configText = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
  const pluginSection = `[plugins."${marketplacePluginRef(plugin)}"]`;
  const marketplaceSection = `[marketplaces.${marketplaceId(plugin)}]`;
  return {
    configPath,
    configExists: fs.existsSync(configPath),
    pluginEnabled: configText.includes(pluginSection) && /enabled\s*=\s*true/.test(sectionText(configText, pluginSection)),
    marketplaceConfigured: configText.includes(marketplaceSection),
    marketplacePath: path.join(path.dirname(payloadRoot), ".agents", "plugins", "marketplace.json"),
    marketplaceExists: fs.existsSync(path.join(path.dirname(payloadRoot), ".agents", "plugins", "marketplace.json")),
  };
}

function inspectClaudeHost(pluginName, payloadRoot) {
  const plugin = { name: pluginName };
  const marketplacePath = path.join(path.dirname(payloadRoot), ".claude-plugin", "marketplace.json");
  const manifestPath = path.join(payloadRoot, ".claude-plugin", "plugin.json");
  return {
    pluginRef: marketplacePluginRef(plugin),
    marketplacePath,
    marketplaceExists: fs.existsSync(marketplacePath),
    manifestPath,
    manifestExists: fs.existsSync(manifestPath),
  };
}

function inspectOpenCodeHost(target, payloadRoot) {
  const configPaths = openCodeConfigPathsForTarget(target);
  const refs = configPaths.map((configPath) => inspectOpenCodeConfig(configPath, payloadRoot));
  return {
    configPaths,
    registered: refs.some((item) => item.referencesPayload),
    configs: refs,
  };
}

function inspectOpenCodeConfig(configPath, payloadRoot) {
  if (!fs.existsSync(configPath)) {
    return { path: configPath, exists: false, referencesPayload: false };
  }
  const parsed = readJsoncFile(configPath);
  if (!parsed) {
    return { path: configPath, exists: true, readable: false, referencesPayload: false };
  }
  const plugins = collectOpenCodePluginEntries(parsed);
  return {
    path: configPath,
    exists: true,
    readable: true,
    referencesPayload: plugins.some((item) => path.resolve(item) === path.resolve(payloadRoot)),
  };
}

function inspectIndependentSkills(targetDir, skillNames) {
  const conflicts = [];
  for (const skillName of skillNames) {
    const skillDir = path.join(targetDir, skillName);
    if (fs.existsSync(path.join(skillDir, "SKILL.md"))) {
      conflicts.push({ name: skillName, path: skillDir });
    }
  }
  return conflicts;
}

function pluginSkillNames(payloadRoot, record) {
  const names = new Set();
  if (record && Array.isArray(record.files)) {
    for (const file of record.files) {
      const rel = path.relative(payloadRoot, file.path);
      const parts = rel.split(path.sep);
      if (parts[0] === "skills" && parts[2] === "SKILL.md") {
        names.add(parts[1]);
      }
    }
  }
  const skillRoot = path.join(payloadRoot, "skills");
  if (fs.existsSync(skillRoot)) {
    for (const entry of fs.readdirSync(skillRoot, { withFileTypes: true })) {
      if (entry.isDirectory() && fs.existsSync(path.join(skillRoot, entry.name, "SKILL.md"))) {
        names.add(entry.name);
      }
    }
  }
  return Array.from(names).sort();
}

function validateRecordFiles(files) {
  let matched = 0;
  let missing = 0;
  let mismatched = 0;
  for (const file of files) {
    if (!file || !file.path || !file.sha256) continue;
    if (!fs.existsSync(file.path)) {
      missing += 1;
      continue;
    }
    if (sha256File(file.path) === file.sha256) {
      matched += 1;
    } else {
      mismatched += 1;
    }
  }
  return { matched, missing, mismatched };
}

function buildFindings(pluginName, registry, targets, options = {}) {
  const findings = [];
  if (!registry.exists) {
    findings.push({ level: "warn", message: `scope registry missing: ${registry.path}` });
  } else if (!registry.readable) {
    findings.push({ level: "fail", message: `scope registry unreadable: ${registry.path}` });
  } else if (!registry.hasPlugin) {
    findings.push({ level: "warn", message: `plugin not recorded in scope registry: ${pluginName}` });
  } else {
    findings.push(...registryPluginFindings(pluginName, registry, options));
  }

  for (const target of targets) {
    const label = `${target.agent}:${target.scope}`;
    if (target.legacyCopy) {
      findings.push({ level: "warn", message: `${label} uses legacy-copy payload: ${target.payloadRoot}` });
    } else {
      findings.push({ level: "info", message: `${label} has no legacy payload` });
    }
    if (target.record && target.fileValidation.missing) {
      findings.push({ level: "fail", message: `${label} has ${target.fileValidation.missing} missing managed file(s)` });
    }
    if (target.record && target.fileValidation.mismatched) {
      findings.push({ level: "fail", message: `${label} has ${target.fileValidation.mismatched} checksum mismatch(es)` });
    }
    for (const conflict of target.independentSkills) {
      findings.push({ level: "warn", message: `${label} has independent skill shadowing plugin skill ${conflict.name}: ${conflict.path}` });
    }
    if (target.legacyCopy) {
      findings.push(...hostFindings(label, target.agent, target.host));
    }
  }
  return findings;
}

function registryPluginFindings(pluginName, registry, options = {}) {
  const findings = [];
  const plugin = registry.plugin;
  const scopeRoot = path.dirname(registry.path);
  const storePath = plugin && plugin.storePath ? path.join(scopeRoot, plugin.storePath) : null;
  if (storePath && !fs.existsSync(storePath)) {
    findings.push({ level: "fail", message: `registry store path missing for ${pluginName}: ${storePath}` });
  }
  const hosts = plugin && plugin.hosts ? plugin.hosts : {};
  for (const [agent, host] of Object.entries(hosts)) {
    if (host.projection) {
      const projectionPath = path.join(scopeRoot, host.projection);
      if (!fs.existsSync(projectionPath)) {
        findings.push({ level: "fail", message: `registry projection missing for ${agent}: ${projectionPath}` });
      }
    }
    if (host.mode === "materialized-copy" || host.mode === "mixed-materialized-copy") {
      const level = options.allowMaterializedCopy ? "warn" : "fail";
      findings.push({
        level,
        message:
          `${agent} projection uses ${host.mode} (content copied instead of linked)` +
          (options.allowMaterializedCopy ? "" : "; rerun with --allow-materialized-copy to acknowledge"),
      });
    }
  }
  return findings;
}

function crossScopeFindings(pluginName, scope, options = {}) {
  const findings = [];
  const otherScope = scope === "project" ? "user" : "project";
  const otherRoot = scope === "project"
    ? path.join(options.homeDir || os.homedir(), ".oh-skillhub")
    : path.join(options.cwd || process.cwd(), ".oh-skillhub");
  const otherRegistryPath = path.join(otherRoot, "registry.json");
  if (!fs.existsSync(otherRegistryPath)) {
    return findings;
  }
  let other = null;
  try {
    other = JSON.parse(fs.readFileSync(otherRegistryPath, "utf8"));
  } catch {
    return findings;
  }
  if (other && other.plugins && other.plugins[pluginName]) {
    const precedence = scope === "project"
      ? "project scope shadows user scope when running inside the project"
      : "user scope install present alongside project scope; project scope takes precedence inside the project";
    findings.push({
      level: "warn",
      message: `plugin ${pluginName} is also installed in ${otherScope} scope: ${otherRegistryPath}; ${precedence}`,
    });
  }
  return findings;
}

function hostFindings(label, agent, host) {
  const findings = [];
  if (agent === "codex") {
    if (!host.marketplaceExists) {
      findings.push({ level: "warn", message: `${label} Codex marketplace file missing: ${host.marketplacePath}` });
    }
    if (!host.pluginEnabled) {
      findings.push({ level: "warn", message: `${label} Codex plugin enable section missing or disabled: ${host.configPath}` });
    }
  }
  if (agent === "claude") {
    if (!host.marketplaceExists) {
      findings.push({ level: "warn", message: `${label} Claude marketplace file missing: ${host.marketplacePath}` });
    }
    if (!host.manifestExists) {
      findings.push({ level: "warn", message: `${label} Claude plugin manifest missing: ${host.manifestPath}` });
    }
  }
  if (agent === "opencode" && !host.registered) {
    findings.push({ level: "warn", message: `${label} OpenCode config does not reference plugin payload` });
  }
  return findings;
}

function renderPluginStatus(report, options = {}) {
  const lines = [`Plugin status: ${report.pluginName}`, `scope: ${report.scope}`];
  lines.push(`registry: ${report.registry.exists ? "present" : "missing"} ${report.registry.path}`);
  if (report.registry.exists && report.registry.readable) {
    lines.push(`registry schema: ${report.registry.schemaVersion || "unknown"}`);
    lines.push(`registry plugin: ${report.registry.hasPlugin ? "present" : "missing"}`);
    if (report.registry.plugin) {
      lines.push(`registry version: ${report.registry.plugin.version || "unknown"}`);
      lines.push(`registry digest: ${report.registry.plugin.contentDigest || "unknown"}`);
      lines.push(`registry store: ${report.registry.plugin.storePath || "unknown"}`);
      const hosts = report.registry.plugin.hosts ? Object.keys(report.registry.plugin.hosts) : [];
      lines.push(`registry hosts: ${hosts.length ? hosts.join(", ") : "none"}`);
    }
  }
  for (const target of report.targets) {
    lines.push("");
    lines.push(`${target.agent}:${target.scope}`);
    lines.push(`  target: ${target.targetDir}`);
    lines.push(`  legacy-copy: ${target.legacyCopy ? "present" : "missing"}`);
    lines.push(`  payload: ${target.payloadRoot}`);
    if (target.record) {
      lines.push(`  record: version ${target.record.version || "unknown"}, files ${target.record.files}, hash ${target.record.contentHash || "unknown"}`);
      lines.push(`  file check: matched ${target.fileValidation.matched}, missing ${target.fileValidation.missing}, mismatched ${target.fileValidation.mismatched}`);
    } else {
      lines.push("  record: missing");
    }
    lines.push(...renderHostStatus(target.agent, target.host));
    if (target.independentSkills.length) {
      lines.push(`  independent skill conflicts: ${target.independentSkills.map((item) => item.name).join(", ")}`);
    } else if (options.verbose) {
      lines.push("  independent skill conflicts: none");
    }
  }
  return lines.join("\n");
}

function renderPluginDoctor(report) {
  const lines = [`Plugin doctor: ${report.pluginName}`, `scope: ${report.scope}`, ""];
  const failCount = report.findings.filter((item) => item.level === "fail").length;
  const warnCount = report.findings.filter((item) => item.level === "warn").length;
  const infoCount = report.findings.filter((item) => item.level === "info").length;
  lines.push(`summary: ${failCount} fail, ${warnCount} warn, ${infoCount} info`);
  for (const finding of report.findings) {
    lines.push(`${finding.level.toUpperCase()}: ${finding.message}`);
  }
  return lines.join("\n");
}

function renderHostStatus(agent, host) {
  if (agent === "codex") {
    return [
      `  codex marketplace: ${host.marketplaceExists ? "present" : "missing"} ${host.marketplacePath}`,
      `  codex config: ${host.pluginEnabled ? "enabled" : "missing/disabled"} ${host.configPath}`,
    ];
  }
  if (agent === "claude") {
    return [
      `  claude ref: ${host.pluginRef}`,
      `  claude marketplace: ${host.marketplaceExists ? "present" : "missing"} ${host.marketplacePath}`,
      `  claude manifest: ${host.manifestExists ? "present" : "missing"} ${host.manifestPath}`,
    ];
  }
  if (agent === "opencode") {
    const configSummary = host.configs
      .filter((item) => item.exists)
      .map((item) => `${item.referencesPayload ? "registered" : "not-registered"} ${item.path}`);
    return [`  opencode config: ${host.registered ? "registered" : "missing"}`, ...configSummary.map((item) => `  ${item}`)];
  }
  return [`  host: unsupported ${agent}`];
}

function sectionText(text, section) {
  const index = text.indexOf(section);
  if (index < 0) return "";
  const rest = text.slice(index + section.length);
  const nextSection = rest.search(/\n\[/);
  return nextSection >= 0 ? rest.slice(0, nextSection) : rest;
}

function readJsoncFile(file) {
  try {
    return JSON.parse(stripJsonComments(stripBom(fs.readFileSync(file, "utf8"))));
  } catch {
    return null;
  }
}

function stripJsonComments(text) {
  return text
    .replace(/\/\*[\s\S]*?\*\//g, "")
    .replace(/(^|\s)\/\/.*$/gm, "$1");
}

module.exports = {
  inspectPlugin,
  renderPluginDoctor,
  renderPluginStatus,
};