const fs = require("fs");
const path = require("path");
const ejs = require("ejs");
const { assetRoots, compareVersions } = require("./tool_loader");

class ToolManager {
  constructor(options = {}) {
    this.repoClient = options.repoClient;
    this.installer = options.installer;
    this.loader = options.loader;
    this.settings = options.settings;
  }

  async list({ force = false } = {}) {
    await this.loader.reconcile({ reload: force });
    const remoteResult = await this.repoClient.discover({ force });
    const localRows = this.installer.scanLocal();
    const remoteMap = new Map(remoteResult.tools.map((metadata) => [metadata.tool_id, metadata]));
    const localMap = new Map(localRows.map((entry) => [entry.tool_id, entry]));
    const ids = [...new Set([...remoteMap.keys(), ...localMap.keys()])].sort();
    return {
      repository: remoteResult.repository,
      branch: remoteResult.branch,
      checked_at: remoteResult.checked_at,
      cached: remoteResult.cached,
      stale: remoteResult.stale,
      error: remoteResult.error || null,
      tools: ids.map((toolId) => {
        const remote = remoteMap.get(toolId) || null;
        const local = localMap.get(toolId) || null;
        const metadata = local?.metadata || remote;
        const installed = Boolean(local);
        const remoteMissing = installed && !remote;
        const enabled = installed && this.loader.isEnabled(toolId);
        const runtime = this.loader.status(toolId);
        const updateAvailable = Boolean(
          installed && remote && local.valid && !remote.remote_invalid &&
          compareVersions(remote.version, local.metadata.version) > 0
        );
        const dependencies = local?.valid ? this.loader.inspectDependencies(local.metadata, local.dir) : { blocking: [], optional: [] };
        return {
          ...metadata,
          installed,
          enabled,
          local_version: installed ? local.metadata.version : null,
          remote_version: remote?.version || null,
          update_available: updateAvailable,
          update_enabled: installed && Boolean(remote) && !remote?.remote_invalid,
          remote_missing: remoteMissing,
          local_only: remoteMissing,
          local_valid: local?.valid !== false,
          local_error: local?.error || null,
          remote_invalid: remote?.remote_invalid === true,
          remote_error: remote?.remote_error || null,
          runtime_state: runtime.state,
          runtime_message: runtime.message,
          registered_tools: [...this.loader.registry.tools.values()]
            .filter((definition) => definition.owning_plugin === toolId)
            .map((definition) => definition.tool_id),
          dependency_status: dependencies,
          primary_type: metadata?.tool_type || "general",
          primary_scope: displayScope(metadata?.scope),
          has_settings: installed && Boolean(metadata?.settings_schema && Object.keys(metadata.settings_schema).length)
        };
      })
    };
  }

  async enable(toolId) {
    let installed = this.installer.local(toolId);
    if (!installed) {
      const remote = await this.repoClient.discover();
      if (!remote.tools.some((entry) => entry.tool_id === toolId && !entry.remote_invalid)) {
        throw new Error("Remote AI tool plugin is unavailable.");
      }
      await this.installer.install(toolId);
      installed = this.installer.local(toolId);
    }
    return this.loader.enable(toolId);
  }

  async disable(toolId) {
    return this.loader.disable(toolId);
  }

  async update(toolId) {
    const local = this.installer.local(toolId);
    if (!local) throw new Error("Install the AI tool plugin before updating it.");
    const remote = await this.repoClient.discover({ force: true });
    if (!remote.tools.some((entry) => entry.tool_id === toolId && !entry.remote_invalid)) {
      throw new Error("This installed tool is missing from the configured repository.");
    }
    const wasEnabled = this.loader.isEnabled(toolId);
    if (wasEnabled) await this.loader.disable(toolId, { persist: false });
    try {
      const result = await this.installer.update(toolId, {
        afterSwap: wasEnabled
          ? async () => {
              const activated = await this.loader.enable(toolId, { persist: false });
              if (!activated?.loaded) throw new Error(activated?.message || "The updated AI tool could not be loaded.");
            }
          : null
      });
      this.loader.setEnabled(toolId, wasEnabled);
      return result;
    } catch (error) {
      if (wasEnabled && this.installer.local(toolId)) {
        try { await this.loader.enable(toolId, { persist: false }); } catch {}
        this.loader.setEnabled(toolId, true);
      }
      throw error;
    }
  }

  async delete(toolId) {
    await this.loader.disable(toolId);
    const deleted = this.installer.delete(toolId);
    this.loader.setEnabled(toolId, false);
    return { deleted };
  }

  async readme(toolId) {
    const local = this.installer.local(toolId);
    if (local) {
      const file = path.join(local.dir, "readme.md");
      if (!fs.existsSync(file)) throw new Error("Installed tool readme.md is missing.");
      return { markdown: fs.readFileSync(file, "utf8"), source: "local" };
    }
    return { markdown: await this.repoClient.readReadme(toolId), source: "remote" };
  }

  settingsFor(toolId) {
    const described = this.settings.describe(toolId);
    const local = this.installer.local(toolId);
    const ui = local?.valid ? settingsUi(local.metadata, local.dir, toolId) : null;
    const status = local?.valid ? settingsStatus(local.metadata, local.dir) : null;
    return { ...described, ui, status };
  }

  resetSettings(toolId) {
    this.settings.reset(toolId);
    return this.loader.isEnabled(toolId)
      ? this.loader.enable(toolId, { persist: false }).then(() => this.settingsFor(toolId))
      : Promise.resolve(this.settingsFor(toolId));
  }

  async saveSettings(toolId, values) {
    const saved = this.settings.save(toolId, values);
    if (this.loader.isEnabled(toolId)) await this.loader.enable(toolId, { persist: false });
    return saved;
  }

  async diagnostics({ role, user, context }) {
    await this.loader.reconcile();
    const exposure = this.loader.registry.inspect({ role, user, context });
    const decisionsByOwner = new Map();
    for (const decision of exposure.considered) {
      const owner = decision.tool.owning_plugin;
      if (!decisionsByOwner.has(owner)) decisionsByOwner.set(owner, []);
      decisionsByOwner.get(owner).push(decision);
    }
    const plugins = this.loader.diagnostics().map((plugin) => {
      const decisions = decisionsByOwner.get(plugin.tool_id) || [];
      const local = this.installer.local(plugin.tool_id);
      const declared = Array.isArray(local?.metadata?.registered_capabilities)
        ? local.metadata.registered_capabilities
        : [];
      let rawSettings = {};
      try { rawSettings = this.settings.readRaw(plugin.tool_id); } catch {}
      const configuredDetails = Object.fromEntries(
        (Array.isArray(local?.metadata?.diagnostic_settings)
          ? local.metadata.diagnostic_settings
          : []
        ).filter((key) => Object.hasOwn(rawSettings, key)).map((key) => [key, rawSettings[key]])
      );
      const persistedStatus = local?.valid ? settingsStatus(local.metadata, local.dir) : {};
      const capabilityDecisions = declared.map((capability) => {
        const registered = decisions.find((decision) => decision.tool.tool_id === capability.tool_id);
        if (registered) return registered;
        const enabled = capability.enabled_setting ? rawSettings[capability.enabled_setting] !== false : true;
        return {
          tool: {
            tool_id: capability.tool_id,
            description: capability.description || "",
            owning_plugin: plugin.tool_id
          },
          exposed: false,
          reason: enabled ? "unavailable" : "disabled",
          message: enabled
            ? "The capability is enabled but not registered."
            : "The capability is disabled in tool settings."
        };
      });
      const allDecisions = [
        ...decisions,
        ...capabilityDecisions.filter((candidate) =>
          !decisions.some((decision) => decision.tool.tool_id === candidate.tool.tool_id)
        )
      ];
      let hiddenReason = null;
      if (!plugin.valid) hiddenReason = "schema_invalid";
      else if (!plugin.enabled) hiddenReason = "disabled";
      else if (plugin.dependencies.blocking.length) hiddenReason = "dependency_failed";
      else if (plugin.state === "unavailable") hiddenReason = "unavailable";
      else if (!allDecisions.some((decision) => decision.exposed)) {
        hiddenReason = allDecisions[0]?.reason || "unavailable";
      }
      return {
        ...plugin,
        runtime_details: {
          ...configuredDetails,
          ...persistedStatus,
          ...(plugin.runtime_details || {})
        },
        prompt_exposed: allDecisions.some((decision) => decision.exposed),
        hidden_reason: hiddenReason,
        decisions: allDecisions
      };
    });
    return {
      role,
      origin: context?.origin || context?.platform || "other",
      considered_tools: plugins.flatMap((plugin) =>
        plugin.decisions.map((decision) => decision.tool.tool_id)
      ),
      exposed_tools: exposure.exposed.map((tool) => tool.tool_id),
      prompt_tools: exposure.exposed,
      plugins
    };
  }

  async loadEnabled() {
    return this.loader.loadEnabled();
  }

  async stopAll() {
    return this.loader.stopAll();
  }

  resolveAsset(toolId, relative, options = {}) {
    if (options.allowInstalled) {
      const local = this.installer.local(toolId);
      if (local?.valid) {
        for (const root of assetRoots(local.metadata, local.dir)) {
          const candidate = path.resolve(root, String(relative || ""));
          if ((candidate === root || candidate.startsWith(`${root}${path.sep}`)) &&
              fs.existsSync(candidate) &&
              fs.statSync(candidate).isFile()) return candidate;
        }
      }
    }
    return this.loader.resolveAsset(toolId, relative);
  }
}

function displayScope(scope) {
  if (typeof scope === "string") return scope;
  if (Array.isArray(scope)) return scope.join(", ");
  if (scope && typeof scope === "object") return scope.label || scope.required_role || JSON.stringify(scope);
  return "unspecified";
}

function settingsUi(metadata, toolDir, toolId) {
  const config = metadata.settings_ui;
  if (!config || typeof config !== "object") return null;
  const view = safeToolPath(toolDir, config.view);
  let html = "";
  if (view && fs.existsSync(view)) {
    html = ejs.render(fs.readFileSync(view, "utf8"), {
      tool: metadata,
      tool_id: toolId
    }, { filename: view });
  }
  return {
    html,
    scripts: safeAssetList(config.scripts, toolId),
    styles: safeAssetList(config.styles, toolId)
  };
}

function safeToolPath(root, relative) {
  if (!relative) return null;
  const target = path.resolve(root, String(relative));
  return target.startsWith(`${path.resolve(root)}${path.sep}`) ? target : null;
}

function safeAssetList(values, toolId) {
  return (Array.isArray(values) ? values : [])
    .map((value) => String(value || "").replace(/^\/+/, ""))
    .filter((value) => value && !value.split("/").includes(".."))
    .map((value) => `/plugins/lumi_ai/tools/${toolId}/assets/${value}`);
}

function settingsStatus(metadata, toolDir) {
  const file = safeToolPath(toolDir, metadata.status_file);
  if (!file || !fs.existsSync(file)) return {};
  try {
    const value = JSON.parse(fs.readFileSync(file, "utf8"));
    return value && typeof value === "object" && !Array.isArray(value) ? value : {};
  } catch {
    return {};
  }
}

module.exports = { ToolManager, displayScope, safeAssetList, settingsStatus, settingsUi };
