import { type Ctor, type Reference } from '@frontmcp/di';

import {
  AdapterEntry,
  AdapterKind,
  FrontMcpLogger,
  type AdapterInterface,
  type AdapterRecord,
  type EntryOwnerRef,
  type FrontMcpAdapterResponse,
} from '../common';
import { InvalidRegistryKindError, RegistryNotInitializedError } from '../errors';
import PromptRegistry from '../prompt/prompt.registry';
import type ProviderRegistry from '../provider/provider.registry';
import ResourceRegistry from '../resource/resource.registry';
import ToolRegistry from '../tool/tool.registry';

export class AdapterInstance extends AdapterEntry {
  readonly deps: Set<Reference>;
  readonly globalProviders: ProviderRegistry;

  private adapterTools: ToolRegistry | null = null;
  private adapterResources: ResourceRegistry | null = null;
  private adapterPrompts: PromptRegistry | null = null;
  private logger?: FrontMcpLogger;
  private unsubscribeUpdate?: () => void;

  constructor(record: AdapterRecord, deps: Set<Reference>, globalProviders: ProviderRegistry) {
    super(record);
    this.deps = deps;
    this.globalProviders = globalProviders;

    this.ready = this.initialize();
  }

  getTools(): ToolRegistry {
    if (!this.adapterTools) throw new RegistryNotInitializedError('AdapterInstance', 'tools');
    return this.adapterTools;
  }

  getResources(): ResourceRegistry {
    if (!this.adapterResources) throw new RegistryNotInitializedError('AdapterInstance', 'resources');
    return this.adapterResources;
  }

  getPrompts(): PromptRegistry {
    if (!this.adapterPrompts) throw new RegistryNotInitializedError('AdapterInstance', 'prompts');
    return this.adapterPrompts;
  }

  protected async initialize() {
    try {
      this.logger = this.globalProviders.get(FrontMcpLogger);
    } catch {
      // Logger not available - optional dependency
    }

    const depsTokens = [...this.deps];
    this.logger?.debug(`Resolving ${depsTokens.length} dependency(ies) for adapter`);

    const depsInstances = await Promise.all(depsTokens.map((t) => this.globalProviders.resolveBootstrapDep(t)));
    const rec = this.record;
    let adapter: AdapterInterface;
    if (rec.kind === AdapterKind.CLASS) {
      const klass = rec.useClass as any;
      adapter = new klass(...depsInstances);
    } else if (rec.kind === AdapterKind.CLASS_TOKEN) {
      const klass = rec.provide as any;
      adapter = new (klass as Ctor<any>)(...depsInstances);
    } else if (rec.kind === AdapterKind.FACTORY) {
      const deps = [...rec.inject()];
      const args: any[] = [];
      for (const d of deps) args.push(await this.globalProviders.resolveBootstrapDep(d));
      adapter = rec.useFactory(...args);
    } else if (rec.kind === AdapterKind.VALUE) {
      adapter = rec.useValue;
    } else {
      throw new InvalidRegistryKindError('adapter', (rec as { kind?: string }).kind);
    }

    this.logger?.debug(`Adapter constructed (kind=${rec.kind})`);
    if (adapter.options['description']) {
      this.logger?.debug(`Adapter description: ${adapter.options['description']}`);
    }

    // Inject logger if adapter supports it
    if (typeof adapter.setLogger === 'function' && this.logger) {
      adapter.setLogger(this.logger.child(`adapter:${adapter.options.name}`));
    }

    this.logger?.debug(`Fetching adapter response from "${adapter.options.name}"`);
    const result = await adapter.fetch();

    const toolCount = result.tools?.length ?? 0;
    const resourceCount = result.resources?.length ?? 0;
    const promptCount = result.prompts?.length ?? 0;
    this.logger?.debug(
      `Adapter "${adapter.options.name}" returned ${toolCount} tool(s), ${resourceCount} resource(s), ${promptCount} prompt(s)`,
    );

    const owner: EntryOwnerRef = {
      kind: 'adapter',
      id: `${adapter.options.name}`,
      ref: rec.provide,
    };

    this.adapterTools = new ToolRegistry(this.globalProviders, result.tools ?? [], owner);
    this.adapterResources = new ResourceRegistry(this.globalProviders, result.resources ?? [], owner);
    this.adapterPrompts = new PromptRegistry(this.globalProviders, result.prompts ?? [], owner);

    await Promise.all([this.adapterTools.ready, this.adapterResources.ready, this.adapterPrompts.ready]);

    this.logger?.debug(`Adapter "${adapter.options.name}" registries initialized`);

    // Subscribe to adapter updates (e.g., OpenAPI spec polling)
    if (typeof adapter.onUpdate === 'function') {
      this.unsubscribeUpdate = adapter.onUpdate((response) => {
        this.handleAdapterUpdate(response, owner);
      });
    }

    // Start polling if adapter supports it
    if (typeof adapter.startPolling === 'function') {
      adapter.startPolling();
      this.logger?.debug(`Adapter "${adapter.options.name}" polling started`);
    }
  }

  /**
   * Handle adapter update (e.g., from spec polling).
   * Replaces all tools/resources/prompts in the child registries.
   */
  private handleAdapterUpdate(response: FrontMcpAdapterResponse, owner: EntryOwnerRef): void {
    this.logger?.debug(`Adapter update received: ${response.tools?.length ?? 0} tool(s)`);

    if (response.tools && this.adapterTools) {
      this.adapterTools.replaceAll(response.tools, owner);
    }
    if (response.resources && this.adapterResources) {
      this.adapterResources.replaceAll(response.resources, owner);
    }
    if (response.prompts && this.adapterPrompts) {
      this.adapterPrompts.replaceAll(response.prompts, owner);
    }
  }
}
