// Download manager state store
// TRACES: UR-011, UR-013, UR-018 | DR-015, DR-017
import { writable, derived, get } from 'svelte/store';
import { commands } from '$lib/api/bindings';
import { listen, type UnlistenFn } from '@tauri-apps/api/event';

// Event listener state
let unlistenFn: UnlistenFn | null = null;
let isEventsInitialized = false;

export interface DownloadInfo {
	id: number;
	itemId: string;
	userId: string;
	filePath: string;
	fileSize?: number;
	mimeType?: string;
	status: 'pending' | 'downloading' | 'completed' | 'failed' | 'paused';
	progress: number;
	bytesDownloaded: number;
	queuedAt: string;
	startedAt?: string;
	completedAt?: string;
	errorMessage?: string;
	retryCount: number;
	priority: number;
	// Item metadata for display (audio)
	itemName?: string;
	artistName?: string;
	albumName?: string;
	// Video-specific metadata
	seriesName?: string;
	seasonName?: string;
	episodeNumber?: number;
	seasonNumber?: number;
	qualityPreset?: string;
	mediaType: 'audio' | 'video';
	// Download source tracking
	downloadSource: 'user' | 'auto';
}

export interface DownloadEvent {
	type:
		| 'queued'
		| 'started'
		| 'progress'
		| 'completed'
		| 'failed'
		| 'paused'
		| 'cancelled'
		| 'waitingForNetwork';
	/** Absent on 'waitingForNetwork', which is queue-wide rather than per-download. */
	downloadId: number;
	itemId: string;
	bytesDownloaded?: number;
	totalBytes?: number;
	progress?: number;
	filePath?: string;
	error?: string;
}

export interface DownloadStats {
	total: number;
	activeCount: number;
	queuedCount: number;
	completedCount: number;
	failedCount: number;
	pausedCount: number;
}

interface DownloadsState {
	downloads: Record<number, DownloadInfo>;
	stats: DownloadStats;
}

/**
 * True when the download queue is held because "WiFi Only" is enabled and the
 * device is on a metered/cellular network. Pending rows stay pending; the queue
 * resumes automatically when an acceptable network appears.
 *
 * TRACES: UR-053 | DR-074
 */
export const waitingForNetwork = writable(false);

function createDownloadsStore() {
	const { subscribe, update, set } = writable<DownloadsState>({
		downloads: {},
		stats: {
			total: 0,
			activeCount: 0,
			queuedCount: 0,
			completedCount: 0,
			failedCount: 0,
			pausedCount: 0
		}
	});

	// Prevent concurrent refresh calls (race condition protection)
	let refreshInProgress = false;
	let pendingRefreshRequest: { userId: string; statusFilter?: string[] } | null = null;

	// Helper function to refresh downloads (avoids `this` binding issues)
	async function refreshDownloads(userId: string, statusFilter?: string[]): Promise<void> {
		// If a refresh is already in progress, queue this request instead
		if (refreshInProgress) {
			console.debug('🔄 Refresh already in progress, queuing request for user:', userId);
			pendingRefreshRequest = { userId, statusFilter };
			return;
		}

		refreshInProgress = true;

		try {
			console.log('🔄 Refreshing downloads for user:', userId);
			const response = (await commands.getDownloads(
				userId,
				statusFilter ?? null
			)) as unknown as { downloads: DownloadInfo[]; stats: DownloadStats };
			console.log('   Got', response.downloads.length, 'downloads from backend');
			console.log('   Stats:', response.stats);

			update((state) => {
				const downloadsMap: Record<number, DownloadInfo> = {};

				for (const download of response.downloads) {
					downloadsMap[download.id] = download;
				}

				// No count calculation - use pre-computed stats from Rust!
				return {
					downloads: downloadsMap,
					stats: response.stats
				};
			});
		} catch (error) {
			console.error('Failed to refresh downloads:', error);
			throw error;
		} finally {
			refreshInProgress = false;

			// Process queued request if any
			if (pendingRefreshRequest) {
				const { userId: queuedUserId, statusFilter: queuedFilter } = pendingRefreshRequest;
				pendingRefreshRequest = null;
				await refreshDownloads(queuedUserId, queuedFilter);
			}
		}
	}

	return {
		subscribe,

		/**
		 * Queue a single item for download
		 */
		async downloadItem(
			itemId: string,
			userId: string,
			filePath: string,
			mimeType?: string,
			priority?: number,
			itemName?: string,
			artistName?: string,
			albumName?: string
		): Promise<number> {
			try {
				console.log('📥 downloadItem called:', { itemId, userId, filePath, itemName, artistName, albumName });
				const downloadId = await commands.downloadItem({
					itemId,
					userId,
					filePath,
					mimeType: mimeType ?? null,
					priority: priority ?? null,
					itemName: itemName ?? null,
					artistName: artistName ?? null,
					albumName: albumName ?? null,
					expectedSize: null
				});
				console.log('   Got download ID from backend:', downloadId);

				// Fetch download info and add to store
				console.log('   Refreshing downloads...');
				await refreshDownloads(userId);
				console.log('   Refresh complete. Store state:', get({ subscribe }));

				return downloadId;
			} catch (error) {
				console.error('Failed to queue download:', error);
				throw error;
			}
		},

		/**
		 * Queue an entire album for download.
		 *
		 * The backend does all of it — listing the album's tracks, queueing them,
		 * resolving each stream URL and starting the queue. It returns the queued
		 * row ids for reporting only; nothing here pairs them back to tracks.
		 *
		 * TRACES: UR-018, UR-055 | DR-173
		 */
		async downloadAlbum(
			handle: string,
			albumId: string,
			userId: string,
			basePath: string
		): Promise<number[]> {
			try {
				console.log('📥 downloadAlbum called:', { albumId, userId, basePath });
				const downloadIds = await commands.downloadAlbum(handle, albumId, userId, basePath);
				console.log('   Got download IDs from backend:', downloadIds);

				// Refresh downloads
				await refreshDownloads(userId);

				return downloadIds;
			} catch (error) {
				console.error('Failed to queue album download:', error);
				throw error;
			}
		},

		/**
		 * Queue a video item (movie/episode) for download with quality preset
		 */
		async downloadVideo(
			itemId: string,
			userId: string,
			filePath: string,
			mimeType?: string,
			priority?: number,
			itemName?: string,
			qualityPreset?: string,
			seriesName?: string,
			seasonName?: string,
			episodeNumber?: number,
			seasonNumber?: number
		): Promise<number> {
			try {
				console.log('🎬 downloadVideo called:', {
					itemId,
					userId,
					filePath,
					itemName,
					qualityPreset,
					seriesName
				});
				const downloadId = await commands.downloadVideo({
					itemId,
					userId,
					filePath,
					mimeType: mimeType ?? null,
					priority: priority ?? null,
					itemName: itemName ?? null,
					qualityPreset: qualityPreset ?? null,
					seriesName: seriesName ?? null,
					seasonName: seasonName ?? null,
					episodeNumber: episodeNumber ?? null,
					seasonNumber: seasonNumber ?? null
				});
				console.log('   Got download ID from backend:', downloadId);

				// Refresh downloads
				await refreshDownloads(userId);

				return downloadId;
			} catch (error) {
				console.error('Failed to queue video download:', error);
				throw error;
			}
		},

		/**
		 * Queue all episodes of a series for download
		 */
		async downloadSeries(
			seriesId: string,
			seriesName: string,
			userId: string,
			basePath: string,
			qualityPreset?: string
		): Promise<number[]> {
			try {
				console.log('📺 downloadSeries called:', {
					seriesId,
					seriesName,
					userId,
					basePath,
					qualityPreset
				});
				const downloadIds = await commands.downloadSeries(
					seriesId,
					seriesName,
					userId,
					basePath,
					qualityPreset ?? null
				);
				console.log('   Queued', downloadIds.length, 'episodes for download');

				// Refresh downloads
				await refreshDownloads(userId);

				return downloadIds;
			} catch (error) {
				console.error('Failed to queue series download:', error);
				throw error;
			}
		},

		/**
		 * Queue all episodes of a season for download
		 */
		async downloadSeason(
			seasonId: string,
			seriesName: string,
			seasonName: string,
			seasonNumber: number,
			userId: string,
			basePath: string,
			qualityPreset?: string
		): Promise<number[]> {
			try {
				console.log('📺 downloadSeason called:', {
					seasonId,
					seriesName,
					seasonName,
					seasonNumber,
					qualityPreset
				});
				const downloadIds = await commands.downloadSeason(
					seasonId,
					seriesName,
					seasonName,
					seasonNumber,
					userId,
					basePath,
					qualityPreset ?? null
				);
				console.log('   Queued', downloadIds.length, 'episodes for download');

				// Refresh downloads
				await refreshDownloads(userId);

				return downloadIds;
			} catch (error) {
				console.error('Failed to queue season download:', error);
				throw error;
			}
		},

		/**
		 * Pin an item's metadata (protects from cache clear)
		 */
		async pinItem(itemId: string): Promise<void> {
			try {
				await commands.pinItem(itemId);
			} catch (error) {
				console.error('Failed to pin item:', error);
				throw error;
			}
		},

		/**
		 * Unpin an item's metadata
		 */
		async unpinItem(itemId: string): Promise<void> {
			try {
				await commands.unpinItem(itemId);
			} catch (error) {
				console.error('Failed to unpin item:', error);
				throw error;
			}
		},

		/**
		 * Check if an item is pinned
		 */
		async isItemPinned(itemId: string): Promise<boolean> {
			try {
				return await commands.isItemPinned(itemId);
			} catch (error) {
				console.error('Failed to check pin status:', error);
				return false;
			}
		},

		/**
		 * Pause a download
		 */
		async pause(downloadId: number): Promise<void> {
			try {
				await commands.pauseDownload(downloadId);
			} catch (error) {
				console.error('Failed to pause download:', error);
				throw error;
			}
		},

		/**
		 * Resume a paused download
		 */
		async resume(downloadId: number): Promise<void> {
			try {
				await commands.resumeDownload(downloadId);
			} catch (error) {
				console.error('Failed to resume download:', error);
				throw error;
			}
		},

		/**
		 * Cancel a download
		 */
		async cancel(downloadId: number): Promise<void> {
			try {
				await commands.cancelDownload(downloadId);
			} catch (error) {
				console.error('Failed to cancel download:', error);
				throw error;
			}
		},

		/**
		 * Delete a completed download
		 */
		async delete(downloadId: number): Promise<void> {
			try {
				await commands.deleteDownload(downloadId);
				update((state) => {
					const { [downloadId]: removed, ...remaining } = state.downloads;
					return { ...state, downloads: remaining };
				});
			} catch (error) {
				console.error('Failed to delete download:', error);
				throw error;
			}
		},

		/**
		 * Refresh downloads list from backend
		 */
		refresh: refreshDownloads,

		/**
		 * Update a specific download in the store (for event handling)
		 */
		updateDownload(downloadId: number, updates: Partial<DownloadInfo>): void {
			update((state) => {
				const download = state.downloads[downloadId];
				if (!download) {
					console.log('   Download not in store:', downloadId);
					return state;
				}

				const updatedDownload = { ...download, ...updates };
				const newDownloads = { ...state.downloads, [downloadId]: updatedDownload };

				console.log('   Store updated for download', downloadId, ':', updates);
				// No count calculation - stats remain as-is until next refresh
				return {
					downloads: newDownloads,
					stats: state.stats
				};
			});
		},

		/**
		 * Remove a download from the store
		 */
		removeDownload(downloadId: number): void {
			update((state) => {
				const { [downloadId]: removed, ...remaining } = state.downloads;
				if (!removed) return state;

				// No count calculation - stats remain as-is until next refresh
				return {
					downloads: remaining,
					stats: state.stats
				};
			});
		}
	};
}

export const downloads = createDownloadsStore();

// Derived stores
export const activeDownloads = derived(downloads, ($d) =>
	Object.values($d.downloads).filter((d) => d.status === 'downloading')
);

export const completedDownloads = derived(downloads, ($d) =>
	Object.values($d.downloads).filter((d) => d.status === 'completed')
);

export const pendingDownloads = derived(downloads, ($d) =>
	Object.values($d.downloads).filter((d) => d.status === 'pending')
);

export const failedDownloads = derived(downloads, ($d) =>
	Object.values($d.downloads).filter((d) => d.status === 'failed')
);

export const videoDownloads = derived(downloads, ($d) =>
	Object.values($d.downloads).filter((d) => d.mediaType === 'video')
);

export const audioDownloads = derived(downloads, ($d) =>
	Object.values($d.downloads).filter((d) => d.mediaType === 'audio' || !d.mediaType)
);

/**
 * Initialize download event listeners.
 * Should be called once when the app starts (e.g., in +layout.svelte).
 */
export async function initDownloadEvents(): Promise<void> {
	if (isEventsInitialized) {
		console.warn('Download events already initialized');
		return;
	}

	try {
		console.log('🎧 Setting up download event listener...');
		unlistenFn = await listen<DownloadEvent>('download-event', (event) => {
			const payload = event.payload;
			console.log('📬 Received download event:', payload.type, 'for download:', payload.downloadId);
			console.log('   Full event payload:', JSON.stringify(payload));

			// Update the store based on event type
			downloads.subscribe((state) => {
				const download = state.downloads[payload.downloadId];
				console.log('   Current download state:', download ? download.status : 'NOT IN STORE');
			})(); // Immediately unsubscribe after reading

			handleDownloadEvent(payload);
		});

		isEventsInitialized = true;
		console.log('✅ Download event listener registered successfully');
	} catch (err) {
		console.error('❌ Failed to register download event listener:', err);
	}
}

/**
 * Clean up download event listeners.
 * Should be called when the app is destroyed.
 */
export function cleanupDownloadEvents(): void {
	if (unlistenFn) {
		unlistenFn();
		unlistenFn = null;
	}
	isEventsInitialized = false;
}

/**
 * Check if the download event listener is initialized.
 */
export function isDownloadEventsInitialized(): boolean {
	return isEventsInitialized;
}

/**
 * Handle a download event and update the store.
 */
function handleDownloadEvent(payload: DownloadEvent): void {
	const currentState = get(downloads);
	const download = currentState.downloads[payload.downloadId];

	switch (payload.type) {
		case 'queued':
			// Just increment queue count - the download will be fetched on refresh
			break;

		case 'started':
			if (download) {
				updateDownloadInStore(payload.downloadId, {
					status: 'downloading',
					startedAt: new Date().toISOString()
				});
			}
			break;

		case 'progress':
			if (download && payload.progress !== undefined) {
				updateDownloadInStore(payload.downloadId, {
					progress: payload.progress,
					bytesDownloaded: payload.bytesDownloaded || download.bytesDownloaded,
					fileSize: payload.totalBytes || download.fileSize
				});
			}
			break;

		case 'completed':
			if (download) {
				// Persist to database
				commands.markDownloadCompleted(
					payload.downloadId,
					payload.totalBytes || download.fileSize || download.bytesDownloaded,
					payload.filePath || download.filePath
				).catch((err) => console.error('Failed to persist download completion:', err));

				updateDownloadInStore(payload.downloadId, {
					status: 'completed',
					progress: 1.0,
					completedAt: new Date().toISOString(),
					filePath: payload.filePath || download.filePath
				});
			}
			break;

		case 'failed':
			if (download) {
				// Persist to database
				commands.markDownloadFailed(
					payload.downloadId,
					payload.error || 'Unknown error'
				).catch((err) => console.error('Failed to persist download failure:', err));

				updateDownloadInStore(payload.downloadId, {
					status: 'failed',
					errorMessage: payload.error
				});
			}
			break;

		case 'paused':
			if (download) {
				updateDownloadInStore(payload.downloadId, {
					status: 'paused'
				});
			}
			break;

		case 'cancelled':
			removeDownloadFromStore(payload.downloadId);
			break;

		case 'waitingForNetwork':
			// Queue-wide, not tied to one download: the pump refused to start
			// anything because WiFi-only is on and we're on a metered network.
			waitingForNetwork.set(true);
			break;
	}

	// Any per-download progress proves the gate isn't holding us any more.
	if (payload.type === 'started' || payload.type === 'progress') {
		waitingForNetwork.set(false);
	}
}

/**
 * Helper to update a download in the store.
 */
function updateDownloadInStore(downloadId: number, updates: Partial<DownloadInfo>): void {
	console.log('   updateDownloadInStore:', downloadId, updates);
	downloads.updateDownload(downloadId, updates);
}

/**
 * Helper to remove a download from the store.
 */
function removeDownloadFromStore(downloadId: number): void {
	console.log('   removeDownloadFromStore:', downloadId);
	downloads.removeDownload(downloadId);
}
