import { writable, derived, type Readable, type Writable } from 'svelte/store';
import { toast } from 'svelte-sonner';

// Core types for the API sync system
export interface EntityState<T = unknown> {
	data: T;
	isLoading: boolean;
	error: string | null;
	lastUpdated: Date;
}

export interface QueuedOperation {
	id: string;
	entityType: string;
	entityId: string;
	operation: 'create' | 'update' | 'delete';
	action?: string;
	data?: unknown;
	retry: number;
	maxRetries: number;
	timestamp: Date;
}

export interface APIResponse<T = unknown> {
	success: boolean;
	data?: T;
	error?: string;
	[key: string]: unknown;
}

// Specific entity state types
export interface FindLikeState {
	isLiked: boolean;
	likeCount: number;
	isLoading: boolean;
	error: string | null;
}

export interface FindState {
	id: string;
	title: string;
	description?: string;
	latitude: string;
	longitude: string;
	locationName?: string;
	category?: string;
	isPublic: boolean;
	createdAt: Date;
	userId: string;
	username: string;
	profilePictureUrl?: string;
	media?: Array<{
		id: string;
		findId: string;
		type: string;
		url: string;
		thumbnailUrl: string | null;
		orderIndex: number | null;
	}>;
	isLikedByUser: boolean;
	likeCount: number;
	commentCount: number;
	isFromFriend: boolean;
}

export interface CommentState {
	id: string;
	findId: string;
	content: string;
	createdAt: Date;
	updatedAt: Date;
	user: {
		id: string;
		username: string;
		profilePictureUrl?: string;
	};
}

export interface FindCommentsState {
	comments: CommentState[];
	commentCount: number;
	isLoading: boolean;
	error: string | null;
}

// Generate unique operation IDs
function generateOperationId(): string {
	return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

// Create operation key for deduplication
function createOperationKey(
	entityType: string,
	entityId: string,
	operation: string,
	action?: string
): string {
	return `${entityType}:${entityId}:${operation}${action ? `:${action}` : ''}`;
}

class APISync {
	// Entity stores - each entity type has its own store
	private entityStores = new Map<string, Writable<Map<string, EntityState>>>();

	// Operation queue for API calls
	private operationQueue = new Map<string, QueuedOperation>();
	private processingQueue = false;

	// Cleanup tracking for memory management
	private subscriptions = new Map<string, Set<() => void>>();

	constructor() {
		// Initialize core entity stores
		this.initializeEntityStore('find');
		this.initializeEntityStore('user');
		this.initializeEntityStore('friendship');
		this.initializeEntityStore('comment');

		// Start processing queue
		this.startQueueProcessor();
	}

	private initializeEntityStore(entityType: string): void {
		if (!this.entityStores.has(entityType)) {
			this.entityStores.set(entityType, writable(new Map<string, EntityState>()));
		}
	}

	private getEntityStore(entityType: string): Writable<Map<string, EntityState>> {
		this.initializeEntityStore(entityType);
		return this.entityStores.get(entityType)!;
	}

	/**
	 * Subscribe to a specific entity's state
	 */
	subscribe<T>(entityType: string, entityId: string): Readable<EntityState<T>> {
		const store = this.getEntityStore(entityType);

		return derived(store, ($entities) => {
			const entity = $entities.get(entityId);
			if (!entity) {
				// Return default state if entity doesn't exist
				return {
					data: null as T,
					isLoading: false,
					error: null,
					lastUpdated: new Date()
				};
			}
			return entity as EntityState<T>;
		});
	}

	/**
	 * Subscribe specifically to find like state
	 */
	subscribeFindLikes(findId: string): Readable<FindLikeState> {
		const store = this.getEntityStore('find');

		return derived(store, ($entities) => {
			const entity = $entities.get(findId);
			if (!entity || !entity.data) {
				return {
					isLiked: false,
					likeCount: 0,
					isLoading: false,
					error: null
				};
			}

			const findData = entity.data as FindState;
			return {
				isLiked: findData.isLikedByUser,
				likeCount: findData.likeCount,
				isLoading: entity.isLoading,
				error: entity.error
			};
		});
	}

	/**
	 * Subscribe to find comments state
	 */
	subscribeFindComments(findId: string): Readable<FindCommentsState> {
		const store = this.getEntityStore('comment');

		return derived(store, ($entities) => {
			const entity = $entities.get(findId);
			if (!entity || !entity.data) {
				return {
					comments: [],
					commentCount: 0,
					isLoading: false,
					error: null
				};
			}

			const commentsData = entity.data as CommentState[];
			return {
				comments: commentsData,
				commentCount: commentsData.length,
				isLoading: entity.isLoading,
				error: entity.error
			};
		});
	}

	/**
	 * Check if entity state exists
	 */
	hasEntityState(entityType: string, entityId: string): boolean {
		const store = this.getEntityStore(entityType);
		let exists = false;

		const unsubscribe = store.subscribe(($entities) => {
			exists = $entities.has(entityId);
		});
		unsubscribe();

		return exists;
	}

	/**
	 * Get current entity state
	 */
	getEntityState<T>(entityType: string, entityId: string): T | null {
		const store = this.getEntityStore(entityType);
		let currentState: T | null = null;

		const unsubscribe = store.subscribe(($entities) => {
			const entity = $entities.get(entityId);
			if (entity?.data) {
				currentState = entity.data as T;
			}
		});
		unsubscribe();

		return currentState;
	}

	/**
	 * Initialize entity state with server data (only if no existing state)
	 */
	setEntityState<T>(entityType: string, entityId: string, data: T, isLoading = false): void {
		const store = this.getEntityStore(entityType);

		store.update(($entities) => {
			const newEntities = new Map($entities);
			newEntities.set(entityId, {
				data,
				isLoading,
				error: null,
				lastUpdated: new Date()
			});
			return newEntities;
		});
	}

	/**
	 * Initialize entity state only if it doesn't exist yet
	 */
	initializeEntityState<T>(entityType: string, entityId: string, data: T, isLoading = false): void {
		if (!this.hasEntityState(entityType, entityId)) {
			this.setEntityState(entityType, entityId, data, isLoading);
		}
	}

	/**
	 * Update entity loading state
	 */
	private setEntityLoading(entityType: string, entityId: string, isLoading: boolean): void {
		const store = this.getEntityStore(entityType);

		store.update(($entities) => {
			const newEntities = new Map($entities);
			const existing = newEntities.get(entityId);
			if (existing) {
				newEntities.set(entityId, {
					...existing,
					isLoading
				});
			}
			return newEntities;
		});
	}

	/**
	 * Update entity error state
	 */
	private setEntityError(entityType: string, entityId: string, error: string): void {
		const store = this.getEntityStore(entityType);

		store.update(($entities) => {
			const newEntities = new Map($entities);
			const existing = newEntities.get(entityId);
			if (existing) {
				newEntities.set(entityId, {
					...existing,
					isLoading: false,
					error
				});
			}
			return newEntities;
		});
	}

	/**
	 * Queue an operation for processing
	 */
	async queueOperation(
		entityType: string,
		entityId: string,
		operation: 'create' | 'update' | 'delete',
		action?: string,
		data?: Record<string, unknown>
	): Promise<void> {
		const operationKey = createOperationKey(entityType, entityId, operation, action);

		// Check if same operation is already queued
		if (this.operationQueue.has(operationKey)) {
			console.log(`Operation ${operationKey} already queued, skipping duplicate`);
			return;
		}

		const queuedOperation: QueuedOperation = {
			id: generateOperationId(),
			entityType,
			entityId,
			operation,
			action,
			data,
			retry: 0,
			maxRetries: 3,
			timestamp: new Date()
		};

		this.operationQueue.set(operationKey, queuedOperation);

		// Set entity to loading state
		this.setEntityLoading(entityType, entityId, true);

		// Process queue if not already processing
		if (!this.processingQueue) {
			this.processQueue();
		}
	}

	/**
	 * Process the operation queue
	 */
	private async processQueue(): Promise<void> {
		if (this.processingQueue || this.operationQueue.size === 0) {
			return;
		}

		this.processingQueue = true;

		const operations = Array.from(this.operationQueue.entries());

		for (const [operationKey, operation] of operations) {
			try {
				await this.executeOperation(operation);
				this.operationQueue.delete(operationKey);
			} catch (error) {
				console.error(`Operation ${operationKey} failed:`, error);

				if (operation.retry < operation.maxRetries) {
					operation.retry++;
					console.log(
						`Retrying operation ${operationKey} (attempt ${operation.retry}/${operation.maxRetries})`
					);
				} else {
					console.error(`Operation ${operationKey} failed after ${operation.maxRetries} retries`);
					this.operationQueue.delete(operationKey);
					this.setEntityError(
						operation.entityType,
						operation.entityId,
						'Operation failed after multiple retries'
					);
					toast.error('Failed to sync changes. Please try again.');
				}
			}
		}

		this.processingQueue = false;

		// If more operations were added while processing, process again
		if (this.operationQueue.size > 0) {
			setTimeout(() => this.processQueue(), 1000); // Wait 1s before retry
		}
	}

	/**
	 * Execute a specific operation
	 */
	private async executeOperation(operation: QueuedOperation): Promise<void> {
		const { entityType, entityId, operation: op, action, data } = operation;

		let response: Response;

		if (entityType === 'find' && action === 'like') {
			// Handle like operations
			const method = (data as { isLiked?: boolean })?.isLiked ? 'POST' : 'DELETE';
			response = await fetch(`/api/finds/${entityId}/like`, {
				method,
				headers: {
					'Content-Type': 'application/json'
				}
			});
		} else if (entityType === 'find' && op === 'update') {
			// Handle find update
			response = await fetch(`/api/finds/${entityId}`, {
				method: 'PUT',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify(data)
			});
		} else if (entityType === 'find' && op === 'delete') {
			// Handle find deletion
			response = await fetch(`/api/finds/${entityId}`, {
				method: 'DELETE',
				headers: {
					'Content-Type': 'application/json'
				}
			});
		} else if (entityType === 'comment' && op === 'create') {
			// Handle comment creation
			response = await fetch(`/api/finds/${entityId}/comments`, {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify(data)
			});
		} else if (entityType === 'comment' && op === 'delete') {
			// Handle comment deletion
			response = await fetch(`/api/finds/comments/${entityId}`, {
				method: 'DELETE',
				headers: {
					'Content-Type': 'application/json'
				}
			});
		} else {
			throw new Error(`Unsupported operation: ${entityType}:${op}:${action}`);
		}

		if (!response.ok) {
			const errorData = await response.json().catch(() => ({}));
			throw new Error(errorData.message || `HTTP ${response.status}`);
		}

		const result = await response.json();

		// Update entity state with successful result
		if (entityType === 'find' && action === 'like') {
			this.updateFindLikeState(entityId, result.isLiked, result.likeCount);
		} else if (entityType === 'find' && op === 'update') {
			// Reload the find data to get the updated state
			// For now, just clear loading state - the parent component handles refresh
			// TODO: Ideally, we'd merge the update data into the existing state
			this.setEntityLoading(entityType, entityId, false);
		} else if (entityType === 'find' && op === 'delete') {
			// Find already removed optimistically, just clear loading state
			this.setEntityLoading(entityType, entityId, false);
		} else if (entityType === 'comment' && op === 'create') {
			this.addCommentToState(result.data.findId, result.data);
		} else if (entityType === 'comment' && op === 'delete') {
			this.removeCommentFromState(entityId, data as { findId: string });
		}
	}

	/**
	 * Update find like state after successful API call
	 */
	private updateFindLikeState(findId: string, isLiked: boolean, likeCount: number): void {
		const store = this.getEntityStore('find');

		store.update(($entities) => {
			const newEntities = new Map($entities);
			const existing = newEntities.get(findId);

			if (existing && existing.data) {
				const findData = existing.data as FindState;
				newEntities.set(findId, {
					...existing,
					data: {
						...findData,
						isLikedByUser: isLiked,
						likeCount: likeCount
					},
					isLoading: false,
					error: null,
					lastUpdated: new Date()
				});
			}

			return newEntities;
		});
	}

	/**
	 * Start the queue processor
	 */
	private startQueueProcessor(): void {
		// Process queue every 100ms
		setInterval(() => {
			if (this.operationQueue.size > 0 && !this.processingQueue) {
				this.processQueue();
			}
		}, 100);
	}

	/**
	 * Toggle like for a find
	 */
	async toggleLike(findId: string): Promise<void> {
		// Get current state for optimistic update
		const store = this.getEntityStore('find');
		let currentState: FindState | null = null;

		const unsubscribe = store.subscribe(($entities) => {
			const entity = $entities.get(findId);
			if (entity?.data) {
				currentState = entity.data as FindState;
			}
		});
		unsubscribe();

		if (!currentState) {
			console.warn(`Cannot toggle like for find ${findId}: find state not found`);
			return;
		}

		// Optimistic update
		const findState = currentState as FindState;
		const newIsLiked = !findState.isLikedByUser;
		const newLikeCount = findState.likeCount + (newIsLiked ? 1 : -1);

		// Update state optimistically
		store.update(($entities) => {
			const newEntities = new Map($entities);
			const existing = newEntities.get(findId);

			if (existing && existing.data) {
				const findData = existing.data as FindState;
				newEntities.set(findId, {
					...existing,
					data: {
						...findData,
						isLikedByUser: newIsLiked,
						likeCount: newLikeCount
					}
				});
			}

			return newEntities;
		});

		// Queue the operation
		await this.queueOperation('find', findId, 'update', 'like', { isLiked: newIsLiked });
	}

	/**
	 * Add comment to find comments state
	 */
	private addCommentToState(findId: string, comment: CommentState): void {
		const store = this.getEntityStore('comment');

		store.update(($entities) => {
			const newEntities = new Map($entities);
			const existing = newEntities.get(findId);

			const comments = existing?.data ? (existing.data as CommentState[]) : [];

			// If this is a real comment from server, remove any temporary comment with the same content
			let updatedComments = comments;
			if (!comment.id.startsWith('temp-')) {
				updatedComments = comments.filter(
					(c) => !(c.id.startsWith('temp-') && c.content === comment.content)
				);
			}

			updatedComments = [comment, ...updatedComments];

			newEntities.set(findId, {
				data: updatedComments,
				isLoading: false,
				error: null,
				lastUpdated: new Date()
			});

			return newEntities;
		});

		// Update find comment count only for temp comments (optimistic updates)
		if (comment.id.startsWith('temp-')) {
			this.updateFindCommentCount(findId, 1);
		}
	}

	/**
	 * Remove comment from find comments state
	 */
	private removeCommentFromState(commentId: string, data: { findId: string }): void {
		const store = this.getEntityStore('comment');

		store.update(($entities) => {
			const newEntities = new Map($entities);
			const existing = newEntities.get(data.findId);

			if (existing?.data) {
				const comments = existing.data as CommentState[];
				const updatedComments = comments.filter((c) => c.id !== commentId);

				newEntities.set(data.findId, {
					...existing,
					data: updatedComments,
					isLoading: false,
					error: null,
					lastUpdated: new Date()
				});
			}

			return newEntities;
		});

		// Update find comment count
		this.updateFindCommentCount(data.findId, -1);
	}

	/**
	 * Update find comment count
	 */
	private updateFindCommentCount(findId: string, delta: number): void {
		const store = this.getEntityStore('find');

		store.update(($entities) => {
			const newEntities = new Map($entities);
			const existing = newEntities.get(findId);

			if (existing && existing.data) {
				const findData = existing.data as FindState;
				newEntities.set(findId, {
					...existing,
					data: {
						...findData,
						commentCount: Math.max(0, findData.commentCount + delta)
					},
					lastUpdated: new Date()
				});
			}

			return newEntities;
		});
	}

	/**
	 * Load comments for a find
	 */
	async loadComments(findId: string): Promise<void> {
		if (this.hasEntityState('comment', findId)) {
			return;
		}

		this.setEntityLoading('comment', findId, true);

		try {
			const response = await fetch(`/api/finds/${findId}/comments`);
			if (!response.ok) {
				throw new Error(`HTTP ${response.status}`);
			}

			const result = await response.json();
			if (result.success) {
				this.setEntityState('comment', findId, result.data, false);
			} else {
				throw new Error(result.error || 'Failed to load comments');
			}
		} catch (error) {
			console.error('Error loading comments:', error);
			this.setEntityError('comment', findId, 'Failed to load comments');
		}
	}

	/**
	 * Add a comment to a find
	 */
	async addComment(findId: string, content: string): Promise<void> {
		// Optimistic update: add temporary comment
		const tempComment: CommentState = {
			id: `temp-${Date.now()}`,
			findId,
			content,
			createdAt: new Date(),
			updatedAt: new Date(),
			user: {
				id: 'current-user',
				username: 'You',
				profilePictureUrl: undefined
			}
		};

		this.addCommentToState(findId, tempComment);

		// Queue the operation
		await this.queueOperation('comment', findId, 'create', undefined, { content });
	}

	/**
	 * Delete a comment
	 */
	async deleteComment(commentId: string, findId: string): Promise<void> {
		// Optimistic update: remove comment
		this.removeCommentFromState(commentId, { findId });

		// Queue the operation
		await this.queueOperation('comment', commentId, 'delete', undefined, { findId });
	}

	/**
	 * Initialize find data from server (only if no existing state)
	 */
	initializeFindData(finds: FindState[]): void {
		for (const find of finds) {
			this.initializeEntityState('find', find.id, find);
		}
	}

	/**
	 * Initialize comments data for a find
	 */
	initializeCommentsData(findId: string, comments: CommentState[]): void {
		this.initializeEntityState('comment', findId, comments);
	}

	/**
	 * Cleanup unused subscriptions (call this when components unmount)
	 */
	cleanup(entityType: string, entityId: string): void {
		const key = `${entityType}:${entityId}`;
		const subscriptions = this.subscriptions.get(key);
		if (subscriptions) {
			subscriptions.forEach((unsubscribe) => unsubscribe());
			this.subscriptions.delete(key);
		}
	}

	/**
	 * Remove find from state after successful deletion
	 */
	private removeFindFromState(findId: string): void {
		const store = this.getEntityStore('find');

		store.update(($entities) => {
			const newEntities = new Map($entities);
			newEntities.delete(findId);
			return newEntities;
		});

		// Also clean up associated comments
		const commentStore = this.getEntityStore('comment');
		commentStore.update(($entities) => {
			const newEntities = new Map($entities);
			newEntities.delete(findId);
			return newEntities;
		});
	}

	/**
	 * Update a find
	 */
	async updateFind(
		findId: string,
		data: {
			title?: string;
			description?: string | null;
			latitude?: number;
			longitude?: number;
			locationName?: string | null;
			category?: string;
			isPublic?: boolean;
			media?: Array<{ type: string; url: string; thumbnailUrl?: string }>;
			mediaToDelete?: string[];
		}
	): Promise<void> {
		// Optimistically update the find state
		const currentState = this.getEntityState<FindState>('find', findId);
		if (currentState) {
			const updatedFind: FindState = {
				...currentState,
				...(data.title !== undefined && { title: data.title }),
				...(data.description !== undefined && { description: data.description || undefined }),
				...(data.latitude !== undefined && { latitude: data.latitude.toString() }),
				...(data.longitude !== undefined && { longitude: data.longitude.toString() }),
				...(data.locationName !== undefined && { locationName: data.locationName || undefined }),
				...(data.category !== undefined && { category: data.category }),
				...(data.isPublic !== undefined && { isPublic: data.isPublic }),
				...(data.media !== undefined && {
					media: data.media.map((m, index) => ({
						id: (m as any).id || `temp-${index}`,
						findId: findId,
						type: m.type,
						url: m.url,
						thumbnailUrl: m.thumbnailUrl || null,
						orderIndex: index
					}))
				})
			};
			this.setEntityState('find', findId, updatedFind, false);
		}

		// Queue the operation
		await this.queueOperation('find', findId, 'update', undefined, data);
	}

	/**
	 * Delete a find
	 */
	async deleteFind(findId: string): Promise<void> {
		// Optimistically remove find from state
		this.removeFindFromState(findId);

		// Queue the operation
		await this.queueOperation('find', findId, 'delete', undefined, {});
	}

	/**
	 * Subscribe to all finds as an array
	 */
	subscribeAllFinds(): Readable<FindState[]> {
		const store = this.getEntityStore('find');

		return derived(store, ($entities) => {
			const finds: FindState[] = [];
			$entities.forEach((entity) => {
				if (entity.data) {
					finds.push(entity.data as FindState);
				}
			});
			// Sort by creation date, newest first
			return finds.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
		});
	}
}

// Create singleton instance
export const apiSync = new APISync();
