import * as fs from "node:fs";
import * as path from "node:path";
import {
	type AnyProvider,
	type ExtensionManifest,
	MetadataProviderRegistry,
	type ProviderType,
} from "@/providers";
import { BaseService } from "../BaseService";
import { BaseExtension } from "./baseExtension";

export class ExtensionService extends BaseService {
	public isRunning = false;

	private readonly providerRegistry = new MetadataProviderRegistry();
	private readonly manifestExtensions = new Map<string, BaseExtension>();

	constructor() {
		super("ExtensionService");
	}

	public override async mount(): Promise<void> {
		await this.loadBuiltInExtensions();
		this.isRunning = true;
	}

	public override async dispose(): Promise<void> {
		await this.deactivateExtensions();
		this.providerRegistry.clear();
		this.manifestExtensions.clear();
		this.isRunning = false;
	}

	/**
	 * Get the modular provider registry
	 */
	public getProviderRegistry(): MetadataProviderRegistry {
		return this.providerRegistry;
	}

	private async loadBuiltInExtensions(): Promise<void> {
		const extensionsPath = path.resolve(process.cwd(), "../../extensions");

		if (!fs.existsSync(extensionsPath)) {
			this.logger.warn(`Extensions directory not found at ${extensionsPath}`);
			return;
		}

		try {
			const extensionDirs = fs
				.readdirSync(extensionsPath, { withFileTypes: true })
				.filter((dirent) => dirent.isDirectory())
				.map((dirent) => dirent.name);

			for (const extensionDir of extensionDirs) {
				await this.loadExtension(extensionDir);
			}
		} catch (error) {
			this.logger.error("Failed to scan extensions directory", error);
		}
	}

	private async loadExtension(extensionName: string): Promise<void> {
		try {
			const extensionPath = `@extensions/${extensionName}`;
			this.logger.debug(`Loading extension from path: ${extensionPath}`);
			const extensionModule = await import(extensionPath);

			// Support both default export and named export
			const ExtensionClass =
				extensionModule.default ||
				extensionModule[Object.keys(extensionModule)[0]];

			if (!ExtensionClass || typeof ExtensionClass !== "function") {
				this.logger.warn(
					`Extension ${extensionName} does not export a valid extension class`,
				);
				return;
			}

			const extensionInstance = new ExtensionClass();

			// Load manifest-based extension
			if (extensionInstance instanceof BaseExtension) {
				await this.loadManifestExtension(extensionInstance);
			} else {
				this.logger.warn(
					`Extension ${extensionName} does not extend BaseExtension`,
				);
			}
		} catch (error) {
			this.logger.error(`Failed to load extension ${extensionName}:`, error);
		}
	}

	/**
	 * Load and register a manifest-based extension
	 */
	private async loadManifestExtension(extension: BaseExtension): Promise<void> {
		try {
			// Get manifest first
			const manifest: ExtensionManifest = extension.getManifest();

			this.logger.info(
				`Loading manifest extension: ${manifest.name} v${manifest.version}`,
			);

			// Initialize extension (creates providers internally)
			if (typeof extension.initialize === "function") {
				await extension.initialize();
			}

			// Now register all providers from extension to server registry
			await this.registerProvidersFromExtension(extension, manifest);

			// Store extension instance
			this.manifestExtensions.set(manifest.name, extension);

			this.logger.success(
				`Extension ${manifest.name} loaded successfully with ${extension.getProviderCount()} providers`,
			);
		} catch (error) {
			this.logger.error("Failed to load manifest extension:", error);
			throw error;
		}
	}

	/**
	 * Create and register all providers from extension manifest
	 * This is called AFTER extension.initialize() completes
	 * Server handles provider instantiation and registration
	 */
	private async registerProvidersFromExtension(
		extension: BaseExtension,
		manifest: ExtensionManifest,
	): Promise<void> {
		const extensionProviders = new Map<ProviderType, AnyProvider>();

		// Iterate over manifest providers and create instances
		for (const providerManifest of manifest.providers) {
			if (providerManifest.enabled !== false) {
				try {
					// Create provider instance with config from manifest
					const ProviderClass = providerManifest.class;
					const providerInstance = new ProviderClass(providerManifest.config);

					// Mount provider lifecycle
					await providerInstance.mount();

					// Add to extension's internal provider map
					extension.addProvider(providerManifest.type, providerInstance);

					const providerKey = `${manifest.name}.${providerManifest.type}`;

					// Register provider to server registry based on type
					switch (providerManifest.type) {
						case "MOVIE":
							this.providerRegistry.registerMovieProvider(
								providerKey,
								providerInstance as import("@/providers").BaseMovieProvider,
							);
							break;
						case "SERIES":
							this.providerRegistry.registerSeriesProvider(
								providerKey,
								providerInstance as import("@/providers").BaseSeriesProvider,
							);
							break;
						case "PERSON":
							this.providerRegistry.registerPersonProvider(
								providerKey,
								providerInstance as import("@/providers").BasePersonProvider,
							);
							break;
					}

					extensionProviders.set(providerManifest.type, providerInstance);

					this.logger.debug(`Registered provider: ${providerKey}`);
				} catch (error) {
					this.logger.error(
						`Failed to create provider ${providerManifest.name}:`,
						error,
					);
				}
			} else {
				this.logger.debug(
					`Provider ${providerManifest.name} is disabled in manifest`,
				);
			}
		}

		// Track extension providers in registry
		this.providerRegistry.registerExtension(manifest.name, extensionProviders);
	}

	private async deactivateExtensions(): Promise<void> {
		// Dispose all manifest-based extensions
		for (const [name, extension] of this.manifestExtensions) {
			try {
				if (typeof extension.dispose === "function") {
					await extension.dispose();
				}
				this.logger.debug(`Disposed manifest extension: ${name}`);
			} catch (error) {
				this.logger.error(`Error while disposing extension ${name}`, error);
			}
		}
	}
}
