import { PlatformError } from './errors.mjs';
import { validateModuleManifest } from './module-contract.mjs';

export class ModuleRegistry {
  #modules = new Map();
  #startOrder = [];
  #ownership = new Map();
  #started = false;

  register({ manifest, lifecycle = {}, source = 'builtin' }, { enabled = true } = {}) {
    const validated = validateModuleManifest(manifest);
    if (this.#modules.has(validated.id)) {
      throw new PlatformError(`Modul bereits registriert: ${validated.id}`, { code: 'MODULE_DUPLICATE' });
    }
    for (const resource of validated.owns) {
      const owner = this.#ownership.get(resource);
      if (owner) throw new PlatformError(`Ressource ${resource} wird bereits von ${owner} besessen.`, { code: 'MODULE_OWNERSHIP_CONFLICT' });
    }
    const active = validated.status !== 'disabled' && enabled !== false;
    const record = {
      manifest: validated,
      lifecycle: {
        start: typeof lifecycle.start === 'function' ? lifecycle.start : async () => {},
        stop: typeof lifecycle.stop === 'function' ? lifecycle.stop : async () => {},
        health: typeof lifecycle.health === 'function' ? lifecycle.health : async () => ({ ok: true }),
      },
      source: String(source || 'builtin'),
      enabled: active,
      state: active ? 'registered' : 'disabled',
      startedAt: null,
      error: null,
    };
    this.#modules.set(validated.id, record);
    for (const resource of validated.owns) this.#ownership.set(resource, validated.id);
    return validated;
  }

  get(id) {
    return this.#modules.get(id) || null;
  }

  has(id) {
    return this.#modules.has(id);
  }

  isEnabled(id) {
    return this.#modules.get(id)?.enabled === true;
  }

  dependentsOf(id, { enabledOnly = false, recursive = true } = {}) {
    const found = new Set();
    const visit = dependency => {
      for (const [candidateId, record] of this.#modules) {
        if (enabledOnly && !record.enabled) continue;
        if (!record.manifest.requires.includes(dependency) || found.has(candidateId)) continue;
        found.add(candidateId);
        if (recursive) visit(candidateId);
      }
    };
    visit(id);
    return [...found];
  }

  #resolveStartOrder() {
    const visiting = new Set();
    const visited = new Set();
    const order = [];
    const visit = id => {
      if (visited.has(id)) return;
      if (visiting.has(id)) throw new PlatformError(`Zyklische Modulabhängigkeit bei ${id}.`, { code: 'MODULE_DEPENDENCY_CYCLE' });
      const record = this.#modules.get(id);
      if (!record) throw new PlatformError(`Fehlende Modulabhängigkeit: ${id}`, { code: 'MODULE_DEPENDENCY_MISSING' });
      if (!record.enabled) return;
      visiting.add(id);
      for (const dependency of record.manifest.requires) {
        const dependencyRecord = this.#modules.get(dependency);
        if (!dependencyRecord) throw new PlatformError(`Fehlende Modulabhängigkeit: ${dependency}`, { code: 'MODULE_DEPENDENCY_MISSING' });
        if (!dependencyRecord.enabled) {
          throw new PlatformError(`Modul ${id} benötigt das deaktivierte Modul ${dependency}.`, {
            code: 'MODULE_DEPENDENCY_DISABLED', details: { module: id, dependency },
          });
        }
        visit(dependency);
      }
      visiting.delete(id);
      visited.add(id);
      order.push(id);
    };
    for (const [id, record] of this.#modules) if (record.enabled) visit(id);
    const capabilities = new Set([...this.#modules.values()].filter(record => record.enabled).flatMap(record => record.manifest.provides));
    for (const record of this.#modules.values()) {
      if (!record.enabled) continue;
      for (const capability of record.manifest.consumes) {
        if (!capabilities.has(capability)) throw new PlatformError(`Fehlende Capability ${capability} für ${record.manifest.id}.`, { code: 'MODULE_CAPABILITY_MISSING' });
      }
    }
    return order;
  }

  validateGraph() {
    const order = this.#resolveStartOrder();
    return Object.freeze({
      order: Object.freeze([...order]),
      enabledModules: Object.freeze(order.map(id => this.#modules.get(id)?.manifest.id).filter(Boolean)),
      capabilities: Object.freeze([...new Set([...this.#modules.values()]
        .filter(record => record.enabled)
        .flatMap(record => record.manifest.provides))].sort()),
    });
  }

  async #startRecord(id, context) {
    const record = this.#modules.get(id);
    if (!record || !record.enabled || record.state === 'running') return;
    for (const dependency of record.manifest.requires) {
      const dependencyRecord = this.#modules.get(dependency);
      if (!dependencyRecord?.enabled) {
        throw new PlatformError(`Modul ${id} benötigt das deaktivierte Modul ${dependency}.`, {
          code: 'MODULE_DEPENDENCY_DISABLED', status: 409, expose: true,
          details: { module: id, dependency },
        });
      }
      if (dependencyRecord.state !== 'running') await this.#startRecord(dependency, context);
    }
    try {
      record.state = 'starting';
      record.error = null;
      await record.lifecycle.start(Object.freeze({ ...context, module: record.manifest }));
      record.state = 'running';
      record.startedAt = new Date().toISOString();
      if (!this.#startOrder.includes(id)) this.#startOrder.push(id);
    } catch (error) {
      record.state = 'failed';
      record.error = error?.message || String(error);
      throw new PlatformError(`Modulstart fehlgeschlagen: ${id}`, { code: 'MODULE_START_FAILED', cause: error, details: { module: id } });
    }
  }

  async startAll(context) {
    this.#startOrder = this.#resolveStartOrder();
    const started = [];
    for (const id of this.#startOrder) {
      const record = this.#modules.get(id);
      if (!record.enabled) continue;
      try {
        await this.#startRecord(id, context);
        started.push(id);
      } catch (error) {
        for (const startedId of started.reverse()) {
          const startedRecord = this.#modules.get(startedId);
          try { await startedRecord.lifecycle.stop(Object.freeze({ ...context, module: startedRecord.manifest, rollback: true })); } catch {}
          startedRecord.state = 'stopped';
        }
        throw error;
      }
    }
    this.#started = true;
  }

  async enable(id, context) {
    const record = this.#modules.get(id);
    if (!record) throw new PlatformError('Modul nicht gefunden.', { code: 'MODULE_NOT_FOUND', status: 404, expose: true });
    if (record.enabled && record.state === 'running') return this.describe(id);
    record.enabled = true;
    record.state = 'registered';
    try {
      this.#resolveStartOrder();
      if (this.#started) await this.#startRecord(id, context);
      return this.describe(id);
    } catch (error) {
      record.enabled = false;
      record.state = 'disabled';
      throw error;
    }
  }

  async disable(id, context, { cascade = false } = {}) {
    const record = this.#modules.get(id);
    if (!record) throw new PlatformError('Modul nicht gefunden.', { code: 'MODULE_NOT_FOUND', status: 404, expose: true });
    if (!record.enabled) return { module: this.describe(id), disabled: [] };
    const dependents = this.dependentsOf(id, { enabledOnly: true, recursive: true });
    if (dependents.length && !cascade) {
      throw new PlatformError('Das Modul wird von aktiven Modulen benötigt.', {
        code: 'MODULE_HAS_ACTIVE_DEPENDENTS', status: 409, expose: true, details: { dependents },
      });
    }
    const orderIndex = new Map(this.#startOrder.map((moduleId, index) => [moduleId, index]));
    const toDisable = [...dependents, id].sort((a, b) => (orderIndex.get(b) ?? -1) - (orderIndex.get(a) ?? -1));
    for (const moduleId of toDisable) {
      const target = this.#modules.get(moduleId);
      if (!target?.enabled) continue;
      if (target.state === 'running') {
        target.state = 'stopping';
        try {
          await target.lifecycle.stop(Object.freeze({ ...context, module: target.manifest, disabled: true }));
          target.state = 'disabled';
        } catch (error) {
          target.state = 'failed';
          target.error = error?.message || String(error);
          throw new PlatformError(`Modul konnte nicht deaktiviert werden: ${moduleId}`, { code: 'MODULE_STOP_FAILED', status: 500, cause: error });
        }
      } else {
        target.state = 'disabled';
      }
      target.enabled = false;
      target.startedAt = null;
    }
    return { module: this.describe(id), disabled: toDisable };
  }

  async unregister(id, context) {
    const record = this.#modules.get(id);
    if (!record) return false;
    if (record.enabled || record.state === 'running') await this.disable(id, context, { cascade: false });
    const dependents = this.dependentsOf(id, { enabledOnly: false, recursive: false });
    if (dependents.length) {
      throw new PlatformError('Modul kann wegen vorhandener Abhängigkeiten nicht entfernt werden.', {
        code: 'MODULE_HAS_DEPENDENTS', status: 409, expose: true, details: { dependents },
      });
    }
    for (const resource of record.manifest.owns) if (this.#ownership.get(resource) === id) this.#ownership.delete(resource);
    this.#modules.delete(id);
    this.#startOrder = this.#startOrder.filter(moduleId => moduleId !== id);
    return true;
  }

  async stopAll(context) {
    for (const id of [...this.#startOrder].reverse()) {
      const record = this.#modules.get(id);
      if (!record || record.state !== 'running') continue;
      try {
        record.state = 'stopping';
        await record.lifecycle.stop(Object.freeze({ ...context, module: record.manifest }));
        record.state = 'stopped';
      } catch (error) {
        record.state = 'failed';
        record.error = error?.message || String(error);
      }
    }
    this.#started = false;
  }

  async health() {
    const checks = [];
    for (const [id, record] of this.#modules) {
      if (!record.enabled || record.state === 'disabled') {
        checks.push({ id, ok: true, state: 'disabled' });
        continue;
      }
      try {
        const result = await record.lifecycle.health({ module: record.manifest });
        checks.push({ id, state: record.state, ok: record.state === 'running' && result?.ok !== false, ...(result || {}) });
      } catch (error) {
        checks.push({ id, state: record.state, ok: false, error: error?.message || String(error) });
      }
    }
    return checks;
  }

  describe(id) {
    const record = this.#modules.get(id);
    if (!record) return null;
    return {
      ...record.manifest,
      source: record.source,
      enabled: record.enabled,
      state: record.state,
      startedAt: record.startedAt,
      error: record.error,
      dependents: this.dependentsOf(id, { enabledOnly: false, recursive: false }),
    };
  }

  snapshot() {
    return [...this.#modules.keys()].map(id => this.describe(id));
  }
}
