import { map, Observable } from 'rxjs';
import { navigationStore } from './navigation';
import { get } from 'svelte/store';

export interface NostrEvent {
	id?: string;
	pubkey: string;
	created_at: number;
	kind: number;
	tags: string[][];
	content: string;
	sig?: string;
}

export interface Filter {
	ids?: string[];
	authors?: string[];
	kinds?: number[];
	since?: number;
	until?: number;
	limit?: number;
	[key: string]: any;
}

export interface Profile {
	name?: string;
	about?: string;
	picture?: string;
	nip05?: string;
	[key: string]: any;
}

export interface Arxlet {
	id: string;
	name: string;
	description?: string;
	icon?: string;
	iconColor?: string;
	iconForegroundColor?: string;
	iconStrokeColor?: string;
	script: string;
	version?: string;
	versionDate: number;
	eventId: string;
}

export interface CCN {
	name: string;
	description: string;
	publicKey: string;
}

export const ws = new WebSocket('ws://localhost:6942');

export class WindowEve {
	#_pubkey: string;

	async publish(event: NostrEvent) {
		let realEvent = event;
		if (!event.sig) realEvent = await this.signEvent(event);
		const response = await fetch('http://localhost:4269/api/events', {
			method: 'PUT',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify(realEvent)
		});
		if (!response.ok) throw new Error('Failed to publish event');
		const data = await response.json();
		if (data.success) return true;
		throw new Error(data.message);
	}

	async getSingleEventById(id: string) {
		const event = await fetch('http://localhost:4269/api/events/' + id);
		if (!event.ok) return null;
		const data = await event.json();
		return data as NostrEvent;
	}

	async getSingleEventWithFilter(filter: Filter): Promise<NostrEvent | null> {
		const events = await this.getAllEventsWithFilter({ ...filter, limit: 1 });
		return events.length > 0 ? events[0] : null;
	}

	async getAllEventsWithFilter(filter: Filter): Promise<NostrEvent[]> {
		const response = await fetch('http://localhost:4269/api/events', {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify(filter)
		});
		if (!response.ok) return [];
		const data = await response.json();
		return data as NostrEvent[];
	}

	subscribeToEvents(filter: Filter) {
		return new Observable<NostrEvent>((subscriber) => {
			const subname = crypto.randomUUID();
			ws.send(JSON.stringify(['REQ', subname, filter]));
			ws.addEventListener('message', (event) => {
				const data = JSON.parse(event.data);
				if (!Array.isArray(data)) return;
				if (data[0] === 'EVENT' && data[1] === subname) subscriber.next(data[2]);
			});
		});
	}

	subscribeToProfile(pubkey: string) {
		return this.subscribeToEvents({
			kinds: [0],
			authors: [pubkey]
		}).pipe(
			map((event) => event.content),
			map((content) => JSON.parse(content)),
			map((profile) => profile as Profile)
		);
	}

	async getProfile(pubkey: string): Promise<Profile | null> {
		try {
			const profile = await fetch(`http://localhost:4269/api/profile/${pubkey}`);
			if (!profile) return null;
			const data = await profile.json();
			return data as Profile;
		} catch (error) {
			console.error(`Failed to parse profile JSON for ${pubkey}:`, error);
			return null;
		}
	}

	getAvatar(pubkey: string): string {
		return `http://localhost:4269/api/avatars/${pubkey}`;
	}

	async signEvent(
		event: Omit<NostrEvent, 'id' | 'sig' | 'pubkey' | 'created_at' | 'tags' | 'content'>
	): Promise<NostrEvent> {
		const realEvent = event as NostrEvent;
		if (!realEvent.created_at) realEvent.created_at = Math.floor(Date.now() / 1000);
		if (!realEvent.tags) realEvent.tags = [];
		if (!realEvent.content) realEvent.content = '';
		const signedEvent = await fetch('http://localhost:4269/api/sign', {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify(realEvent)
		});
		if (!signedEvent.ok) throw new Error('Failed to sign event');
		const data = await signedEvent.json();
		return data as NostrEvent;
	}

	async getPublicKey(): Promise<string> {
		if (this.#_pubkey) return this.#_pubkey;
		const pubkey = await fetch('http://localhost:4269/api/pubkey');
		if (!pubkey.ok) throw new Error('Failed to fetch public key');
		const data = await pubkey.json();
		this.#_pubkey = data.pubkey as string;
		return this.#_pubkey;
	}

	async getAllArxlets(): Promise<Arxlet[]> {
		const response = await fetch('http://localhost:4269/api/arxlets');
		if (!response.ok) return [];
		const data = await response.json();
		return data as Arxlet[];
	}

	async getArxlet(id: string): Promise<Arxlet | null> {
		const arxlet = await fetch(`http://localhost:4269/api/arxlets/${id}`);
		if (!arxlet) return null;
		const data = await arxlet.json();
		if (data.error) return null;
		return data as Arxlet;
	}

	async getRemoteArxlets(): Promise<Arxlet[]> {
		const response = await fetch('http://localhost:4269/api/arxlets-available');
		if (!response.ok) return [];
		const data = await response.json();
		return data as Arxlet[];
	}

	async cloneRemoteEvent(eventId: string): Promise<NostrEvent> {
		const response = await fetch(`http://localhost:4269/api/clone-remote-event/${eventId}`);
		if (!response.ok) throw new Error('Failed to fetch event');
		const data = await response.json();
		if (data.error) throw new Error(data.error);
		const event = data as NostrEvent;
		return await this.signEvent(event);
	}

	async listCCNs(): Promise<CCN[]> {
		const response = await fetch('http://localhost:4269/api/ccns');
		if (!response.ok) return [];
		const data = await response.json();
		return data as CCN[];
	}

	getCCNIcon(pubkey: string): string {
		return `http://localhost:4269/api/ccns/icon/${pubkey}`;
	}

	getCCNAvatar(pubkey: string): string {
		return `http://localhost:4269/api/ccns/avatar/${pubkey}`;
	}

	async getActiveCCN(): Promise<CCN | null> {
		const response = await fetch('http://localhost:4269/api/ccns/active');
		if (!response.ok) return null;
		const data = await response.json();
		return data as CCN;
	}

	async activateCCN(pubkey: string): Promise<void> {
		const response = await fetch(`http://localhost:4269/api/ccns/active`, {
			method: 'POST',
			body: JSON.stringify({ pubkey })
		});
		if (!response.ok) throw new Error('Failed to activate CCN');
		const data = await response.json();
		if (data.error) throw new Error(data.error);
	}

	async createCCN(name: string, description: string): Promise<CCN> {
		const response = await fetch('http://localhost:4269/api/ccns/new', {
			method: 'POST',
			body: JSON.stringify({ name, description })
		});
		if (!response.ok) throw new Error('Failed to create CCN');
		const data = await response.json();
		if (data.error) throw new Error(data.error);
		return data as CCN;
	}

	async joinCCN(
		version: number,
		startIndex: string,
		name: string,
		description: string,
		key: Uint8Array
	): Promise<CCN> {
		console.log(key);
		const response = await fetch('http://localhost:4269/api/ccns/join', {
			method: 'POST',
			body: JSON.stringify({ version, startIndex, name, description, key: [...key] })
		});
		if (!response.ok) throw new Error('Failed to join CCN');
		const data = await response.json();
		if (data.error) throw new Error(data.error);
		return data as CCN;
	}

	get currentUrl() {
		const c = get(navigationStore);
		const url = new URL('eve://' + c.history[c.currentIndex].slice(1));
		if (url.hostname === '') url.hostname = 'home';
		return url;
	}

	async generateInviteCode(): Promise<string> {
		const response = await fetch('http://localhost:4269/api/ccns/active/invite');
		if (!response.ok) throw new Error('Failed to generate invite code');
		const data = await response.json();
		if (data.error) throw new Error(data.error);
		return data.invite as string;
	}

	async getReputation(user: string): Promise<number> {
		const response = await fetch(`http://localhost:4269/api/reputation/${user}`);
		if (!response.ok) throw new Error('Failed to get reputation');
		const data = await response.json();
		if (data.error) throw new Error(data.error);
		return data.reputation as number;
	}
}

declare global {
	interface Window {
		eve: WindowEve;
	}
}

window.eve = new WindowEve();
