import type { Bus } from "../bus/bus.js";
import type { AuthContract } from "../contracts/auth.js";
import type {
  ConfigAccess,
  EventsEmitter,
  Extension,
  HostAPI,
  Manifest,
  PermissionGate,
  ScheduledJob,
  SecretsAccess,
  StorageNamespace,
} from "../contracts/extension.js";
import type {
  EventHandler,
  EventHookDescriptor,
  FilterDescriptor,
  FilterHandler,
  ServiceHandle,
} from "../contracts/hooks.js";
import type { LogDeps, Logger, LogSink } from "../contracts/logging.js";
import type { ProviderContract } from "../contracts/provider.js";
import type { ToolContract } from "../contracts/tool.js";
import { createLogger } from "../logging/logger.js";
import { resolveActivationOrder } from "./dag.js";
import { isApiVersionCompatible } from "./version.js";

export const KERNEL_API_VERSION = "0.1.0";

export interface DisabledExtension {
  readonly manifest: Manifest;
  readonly reason: string;
}

export interface HostDeps {
  readonly logger: Logger;
  readonly config: ConfigAccess;
  readonly storageFactory: (namespace: string) => StorageNamespace;
  readonly secrets: SecretsAccess;
  readonly permissions: PermissionGate;
  readonly scheduler: { readonly register: (job: ScheduledJob) => void };
  readonly bus: Bus;
  readonly events: EventsEmitter;
  readonly logSink: LogSink;
  readonly logDeps: LogDeps;
}

export interface Host {
  readonly activate: () => Promise<void>;
  readonly deactivate: () => Promise<void>;
  readonly getTools: () => ReadonlyMap<string, ToolContract>;
  readonly getTool: (name: string) => ToolContract | undefined;
  readonly getProviders: () => ReadonlyMap<string, ProviderContract>;
  readonly getProvider: (id: string) => ProviderContract | undefined;
  readonly getAuthProviders: () => ReadonlyMap<string, AuthContract>;
  readonly getAuthProvider: (id: string) => AuthContract | undefined;
  readonly getScheduledJobs: () => readonly ScheduledJob[];
  readonly getMigrations: () => readonly string[];
  readonly getDisabled: () => readonly DisabledExtension[];
  readonly getExtensions: () => readonly Manifest[];
  readonly getHostAPI: () => HostAPI;
}

export function createHost(extensions: readonly Extension[], deps: HostDeps): Host {
  const tools = new Map<string, ToolContract>();
  const providers = new Map<string, ProviderContract>();
  const authProviders = new Map<string, AuthContract>();
  const scheduledJobs: ScheduledJob[] = [];
  const migrations: string[] = [];
  const disabled: DisabledExtension[] = [];
  const activated: Extension[] = [];

  const ordered = resolveActivationOrder(extensions.map((e) => e.manifest));
  const extById = new Map<string, Extension>();
  for (const ext of extensions) {
    extById.set(ext.manifest.id, ext);
  }

  const compatible: Extension[] = [];
  for (const m of ordered) {
    const ext = extById.get(m.id);
    if (ext === undefined) continue;
    if (isApiVersionCompatible(m.apiVersion, KERNEL_API_VERSION)) {
      compatible.push(ext);
    } else {
      disabled.push({
        manifest: m,
        reason: `apiVersion "${m.apiVersion}" is incompatible with kernel API ${KERNEL_API_VERSION}`,
      });
      deps.logger.warn(`Extension "${m.id}" disabled: apiVersion incompatible`);
    }
  }

  for (const ext of compatible) {
    const extMigrations = ext.manifest.contributes?.migrations;
    if (extMigrations) {
      for (const migration of extMigrations) {
        migrations.push(migration);
      }
    }
  }

  function buildHostAPI(
    extensionId: string,
    opts?: { readonly registrationClosed?: boolean },
  ): HostAPI {
    const closed = opts?.registrationClosed ?? false;
    const extLogger = createLogger({ extensionId }, deps.logSink, deps.logDeps);
    return {
      defineTool(tool: ToolContract) {
        if (closed) throw new Error("Registration not available after activation");
        tools.set(tool.name, tool);
      },
      defineProvider(provider: ProviderContract) {
        if (closed) throw new Error("Registration not available after activation");
        providers.set(provider.id, provider);
      },
      defineAuth(auth: AuthContract) {
        if (closed) throw new Error("Registration not available after activation");
        authProviders.set(auth.id, auth);
      },
      on<TPayload>(hook: EventHookDescriptor<TPayload>, handler: EventHandler<TPayload>) {
        return deps.bus.on(hook, handler);
      },
      emit<TPayload>(hook: EventHookDescriptor<TPayload>, payload: TPayload) {
        deps.bus.emit(hook, payload);
      },
      addFilter<TValue>(hook: FilterDescriptor<TValue>, fn: FilterHandler<TValue>) {
        return deps.bus.addFilter(hook, fn);
      },
      async applyFilters<TValue>(
        hook: FilterDescriptor<TValue>,
        value: TValue,
        opts?: { readonly failClosed?: boolean },
      ): Promise<TValue> {
        return deps.bus.applyFilters(hook, value, opts);
      },
      provideService<T>(handle: ServiceHandle<T>, impl: T) {
        deps.bus.provideService(handle, impl);
      },
      getService<T>(handle: ServiceHandle<T>): T {
        return deps.bus.getService(handle);
      },
      storage(namespace: string): StorageNamespace {
        return deps.storageFactory(namespace);
      },
      config: deps.config,
      secrets: deps.secrets,
      permissions: deps.permissions,
      events: deps.events,
      logger: extLogger,
      getProviders() {
        return providers;
      },
      getTools() {
        return tools;
      },
      getAuthProviders() {
        return authProviders;
      },
      getAuthProvider(id: string) {
        return authProviders.get(id);
      },
      getExtensions() {
        return Object.freeze(activated.map((e) => e.manifest));
      },
      scheduler: {
        register(job: ScheduledJob) {
          scheduledJobs.push(job);
          deps.scheduler.register(job);
        },
      },
    };
  }

  return {
    async activate() {
      for (const ext of compatible) {
        try {
          await ext.activate(buildHostAPI(ext.manifest.id));
          activated.push(ext);
          deps.logger.info(`Extension "${ext.manifest.id}" activated`);
        } catch (err) {
          disabled.push({
            manifest: ext.manifest,
            reason: `Activation failed: ${err instanceof Error ? err.message : String(err)}`,
          });
          deps.logger.error(`Extension "${ext.manifest.id}" failed to activate`, { err });
        }
      }
    },
    async deactivate() {
      for (let i = activated.length - 1; i >= 0; i--) {
        const ext = activated[i];
        if (ext === undefined || ext.deactivate === undefined) continue;
        try {
          await ext.deactivate();
        } catch (err) {
          deps.logger.error(`Extension "${ext.manifest.id}" failed to deactivate`, { err });
        }
      }
    },
    getTools() {
      return tools;
    },
    getTool(name: string) {
      return tools.get(name);
    },
    getProviders() {
      return providers;
    },
    getProvider(id: string) {
      return providers.get(id);
    },
    getAuthProviders() {
      return authProviders;
    },
    getAuthProvider(id: string) {
      return authProviders.get(id);
    },
    getScheduledJobs() {
      return scheduledJobs;
    },
    getMigrations() {
      return migrations;
    },
    getDisabled() {
      return disabled;
    },
    getExtensions() {
      return Object.freeze(activated.map((e) => e.manifest));
    },
    getHostAPI() {
      return buildHostAPI("__host__", { registrationClosed: true });
    },
  };
}
