/**
 * PCD Manager - High-level orchestration for PCD lifecycle
 */

import { Git, clone, type GitStatus, type UpdateInfo } from '$utils/git/index.ts';
import { databaseInstancesQueries } from '$db/queries/databaseInstances.ts';
import type { DatabaseInstance } from '$db/queries/databaseInstances.ts';
import { loadManifest, type Manifest } from './manifest.ts';
import { getPCDPath } from './paths.ts';
import { processDependencies, syncDependencies } from './deps.ts';
import { notify } from '$notifications/builder.ts';
import { NotificationTypes } from '$notifications/types.ts';
import { compile, invalidate, startWatch, getCache } from './cache.ts';
import { logger } from '$logger/logger.ts';

export interface LinkOptions {
	repositoryUrl: string;
	name: string;
	branch?: string;
	syncStrategy?: number;
	autoPull?: boolean;
	personalAccessToken?: string;
}

export interface SyncResult {
	success: boolean;
	commitsBehind: number;
	error?: string;
}

/**
 * PCD Manager - Manages the lifecycle of Profilarr Compliant Databases
 */
class PCDManager {
	/**
	 * Link a new PCD repository
	 */
	async link(options: LinkOptions): Promise<DatabaseInstance> {
		await logger.debug('Starting database link operation', {
			source: 'PCDManager',
			meta: {
				name: options.name,
				repositoryUrl: options.repositoryUrl,
				branch: options.branch
			}
		});

		// Generate UUID for storage
		const uuid = crypto.randomUUID();
		const localPath = getPCDPath(uuid);

		try {
			// Clone the repository and detect if it's private
			const isPrivate = await clone(options.repositoryUrl, localPath, options.branch, options.personalAccessToken);

			// Validate manifest (loadManifest throws if invalid)
			await loadManifest(localPath);

			// Process dependencies (clone and validate)
			await processDependencies(localPath);

			// Insert into database
			const id = databaseInstancesQueries.create({
				uuid,
				name: options.name,
				repositoryUrl: options.repositoryUrl,
				localPath,
				syncStrategy: options.syncStrategy,
				autoPull: options.autoPull,
				personalAccessToken: options.personalAccessToken,
				isPrivate
			});

			// Get and return the created instance
			const instance = databaseInstancesQueries.getById(id);
			if (!instance) {
				throw new Error('Failed to retrieve created database instance');
			}

			// Compile cache and start watching (only if enabled)
			if (instance.enabled) {
				try {
					const stats = await compile(localPath, id);
					await startWatch(localPath, id);

					await logger.debug(`Cache compiled for "${options.name}"`, {
						source: 'PCDManager',
						meta: {
							databaseId: id,
							schema: stats.schema,
							base: stats.base,
							tweaks: stats.tweaks,
							user: stats.user
						}
					});
				} catch (error) {
					// Log error but don't fail the link operation
					await logger.error('Failed to compile PCD cache after linking', {
						source: 'PCDManager',
						meta: { error: String(error), databaseId: id }
					});
				}
			}

			await notify(NotificationTypes.PCD_LINKED)
				.title('Database Linked')
				.message(`Database "${options.name}" has been linked successfully`)
				.meta({ databaseId: id, databaseName: options.name, repositoryUrl: options.repositoryUrl })
				.send();

			return instance;
		} catch (error) {
			// Cleanup on failure - remove cloned directory
			try {
				await Deno.remove(localPath, { recursive: true });
			} catch {
				// Ignore cleanup errors
			}
			throw error;
		}
	}

	/**
	 * Unlink a PCD repository
	 */
	async unlink(id: number): Promise<void> {
		const instance = databaseInstancesQueries.getById(id);
		if (!instance) {
			throw new Error(`Database instance ${id} not found`);
		}

		// Store name and URL for notification
		const { name, repository_url } = instance;

		// Invalidate cache first
		invalidate(id);

		// Delete from database
		databaseInstancesQueries.delete(id);

		// Then cleanup filesystem
		try {
			await Deno.remove(instance.local_path, { recursive: true });
		} catch (error) {
			// Log but don't throw - database entry is already deleted
			console.error(`Failed to remove PCD directory ${instance.local_path}:`, error);
		}

		await notify(NotificationTypes.PCD_UNLINKED)
			.title('Database Unlinked')
			.message(`Database "${name}" has been removed`)
			.meta({ databaseId: id, databaseName: name, repositoryUrl: repository_url })
			.send();
	}

	/**
	 * Sync a PCD repository (pull updates)
	 */
	async sync(id: number): Promise<SyncResult> {
		const instance = databaseInstancesQueries.getById(id);
		if (!instance) {
			throw new Error(`Database instance ${id} not found`);
		}

		const git = new Git(instance.local_path);

		try {
			// Check for updates first
			const updateInfo = await git.checkForUpdates();

			if (!updateInfo.hasUpdates) {
				// Already up to date
				databaseInstancesQueries.updateSyncedAt(id);
				return {
					success: true,
					commitsBehind: 0
				};
			}

			// Pull updates
			await git.pull();

			// Sync dependencies (schema, etc.) if versions changed
			await syncDependencies(instance.local_path);

			// Update last_synced_at
			databaseInstancesQueries.updateSyncedAt(id);

			// Recompile cache and restart watching (only if enabled)
			if (instance.enabled) {
				try {
					await compile(instance.local_path, id);
					await startWatch(instance.local_path, id);
				} catch (error) {
					await logger.error('Failed to recompile PCD cache after sync', {
						source: 'PCDManager',
						meta: { error: String(error), databaseId: id }
					});
				}
			}

			return {
				success: true,
				commitsBehind: updateInfo.commitsBehind
			};
		} catch (error) {
			return {
				success: false,
				commitsBehind: 0,
				error: error instanceof Error ? error.message : 'Unknown error'
			};
		}
	}

	/**
	 * Check for available updates without pulling
	 */
	async checkForUpdates(id: number): Promise<UpdateInfo> {
		const instance = databaseInstancesQueries.getById(id);
		if (!instance) {
			throw new Error(`Database instance ${id} not found`);
		}

		const git = new Git(instance.local_path);
		return await git.checkForUpdates();
	}

	/**
	 * Get parsed manifest for a PCD
	 */
	async getManifest(id: number): Promise<Manifest> {
		const instance = databaseInstancesQueries.getById(id);
		if (!instance) {
			throw new Error(`Database instance ${id} not found`);
		}

		return await loadManifest(instance.local_path);
	}

	/**
	 * Switch branch for a PCD
	 */
	async switchBranch(id: number, branch: string): Promise<void> {
		const instance = databaseInstancesQueries.getById(id);
		if (!instance) {
			throw new Error(`Database instance ${id} not found`);
		}

		const git = new Git(instance.local_path);
		await git.checkout(branch);
		await git.pull();
		databaseInstancesQueries.updateSyncedAt(id);
	}

	/**
	 * Get git status for a PCD
	 */
	async getStatus(id: number): Promise<GitStatus> {
		const instance = databaseInstancesQueries.getById(id);
		if (!instance) {
			throw new Error(`Database instance ${id} not found`);
		}

		const git = new Git(instance.local_path);
		return await git.status();
	}

	/**
	 * Get all PCDs
	 */
	getAll(): DatabaseInstance[] {
		return databaseInstancesQueries.getAll();
	}

	/**
	 * Get PCD by ID
	 */
	getById(id: number): DatabaseInstance | undefined {
		return databaseInstancesQueries.getById(id);
	}

	/**
	 * Get PCDs that need auto-sync
	 */
	getDueForSync(): DatabaseInstance[] {
		return databaseInstancesQueries.getDueForSync();
	}

	/**
	 * Initialize PCD caches for all enabled databases
	 * Should be called on application startup
	 */
	async initialize(): Promise<void> {
		const startTime = performance.now();

		await logger.debug('Initialize caches', { source: 'PCDManager' });

		const instances = databaseInstancesQueries.getAll();
		const enabledInstances = instances.filter((instance) => instance.enabled);

		// Collect results for aggregate logging
		const results: Array<{
			name: string;
			schema: number;
			base: number;
			tweaks: number;
			user: number;
			error?: string;
		}> = [];

		// Compile and watch all enabled instances
		for (const instance of enabledInstances) {
			try {
				const stats = await compile(instance.local_path, instance.id);
				await startWatch(instance.local_path, instance.id);

				results.push({
					name: instance.name,
					schema: stats.schema,
					base: stats.base,
					tweaks: stats.tweaks,
					user: stats.user
				});
			} catch (error) {
				results.push({
					name: instance.name,
					schema: 0,
					base: 0,
					tweaks: 0,
					user: 0,
					error: String(error)
				});

				await logger.error(`Cache failed "${instance.name}"`, {
					source: 'PCDManager',
					meta: { error: String(error), databaseId: instance.id }
				});
			}
		}

		const timing = Math.round(performance.now() - startTime);
		const successful = results.filter((r) => !r.error);

		await logger.info('Caches ready', {
			source: 'PCDManager',
			meta: {
				databases: successful.map((r) => ({
					name: r.name,
					schema: r.schema,
					base: r.base,
					tweaks: r.tweaks,
					user: r.user
				})),
				timing: `${timing}ms`
			}
		});
	}

	/**
	 * Get the cache for a database instance
	 */
	getCache(id: number) {
		return getCache(id);
	}
}

// Export singleton instance
export const pcdManager = new PCDManager();
