// External imports
import { TLStore, getSnapshot, Editor, loadSnapshot } from '@tldraw/tldraw';
import logger from '../../debugConfig';
import { SharedStoreService } from './sharedStoreService';

export interface LoadingState {
    status: 'loading' | 'ready' | 'error';
    error: string;
}

const SUPABASE_URL = import.meta.env.VITE_SUPABASE_URL as string;
const SUPABASE_ANON_KEY = import.meta.env.VITE_SUPABASE_ANON_KEY as string;
const BUCKET = 'cc.users';

async function storageGet(path: string, accessToken: string): Promise<unknown | null> {
    const url = `${SUPABASE_URL}/storage/v1/object/authenticated/${BUCKET}/${path}`;
    const res = await fetch(url, {
        headers: {
            Authorization: `Bearer ${accessToken}`,
            apikey: SUPABASE_ANON_KEY,
        },
    });
    if (res.status === 404 || res.status === 400) return null;
    if (!res.ok) throw new Error(`Storage GET ${res.status}: ${await res.text()}`);
    return res.json();
}

async function storagePut(path: string, accessToken: string, data: unknown): Promise<void> {
    const url = `${SUPABASE_URL}/storage/v1/object/${BUCKET}/${path}`;
    const headers = {
        Authorization: `Bearer ${accessToken}`,
        apikey: SUPABASE_ANON_KEY,
        'Content-Type': 'application/json',
    };
    const body = JSON.stringify(data);
    // PUT replaces an existing object; POST creates a new one.
    // Avoids x-upsert custom header which self-hosted Supabase CORS may block.
    let res = await fetch(url, { method: 'PUT', headers, body });
    if (!res.ok && (res.status === 404 || res.status === 400)) {
        res = await fetch(url, { method: 'POST', headers, body });
    }
    if (!res.ok) throw new Error(`Storage ${res.status}: ${await res.text()}`);
}

export class NavigationSnapshotService {
    private store: TLStore;
    private editor: Editor | null = null;
    private currentNodePath: string | null = null;
    private _accessToken: string | null = null;
    private isAutoSaveEnabled = true;
    private isSaving = false;
    private isLoading = false;
    private pendingOperation: { save?: string; load?: string } | null = null;
    private debounceTimeout: ReturnType<typeof setTimeout> | null = null;

    constructor(store: TLStore, editor?: Editor) {
        this.store = store;
        this.editor = editor || null;
        logger.debug('snapshot-service', '🔄 Initialized NavigationSnapshotService', {
            storeId: store.id,
            hasEditor: !!editor,
        });
    }

    setEditor(editor: Editor): void {
        this.editor = editor;
    }

    setAccessToken(token: string): void {
        this._accessToken = token;
    }

    static async loadNodeSnapshotFromDatabase(
        nodePath: string,
        accessToken: string,
        store: TLStore,
        setLoadingState: (state: LoadingState) => void,
        sharedStore?: SharedStoreService,
        editor?: Editor
    ): Promise<void> {
        try {
            setLoadingState({ status: 'loading', error: '' });
            logger.info('snapshot-service', '📂 Loading snapshot from Storage', { path: nodePath });

            const snapshot = await storageGet(nodePath, accessToken);

            if (!snapshot) {
                logger.debug('snapshot-service', 'ℹ️ No snapshot found at path — clearing canvas', { nodePath });
                // Clear all shapes so the canvas is blank for this new node
                if (editor) {
                    const shapeIds = [...editor.getCurrentPageShapeIds()];
                    if (shapeIds.length > 0) {
                        editor.deleteShapes(shapeIds);
                    }
                }
                setLoadingState({ status: 'ready', error: '' });
                return;
            }

            const snap = snapshot as { document?: unknown; session?: unknown; schemaVersion?: unknown };
            if (!snap.document || !snap.session) {
                logger.warn('snapshot-service', '⚠️ Invalid snapshot format at path', { nodePath });
                setLoadingState({ status: 'ready', error: '' });
                return;
            }

            if (sharedStore) {
                await sharedStore.loadSnapshot(snapshot, setLoadingState);
                return;
            }

            const snapshotCopy = {
                schemaVersion: snap.schemaVersion || (snap.document as { schema?: { schemaVersion?: unknown } })?.schema?.schemaVersion,
                document: snap.document,
                session: snap.session,
            };

            try {
                if (editor) {
                    loadSnapshot(editor.store, snapshotCopy as Parameters<typeof loadSnapshot>[1]);
                } else {
                    loadSnapshot(store, snapshotCopy as Parameters<typeof loadSnapshot>[1]);
                }
                logger.debug('snapshot-service', '✅ Snapshot loaded successfully');
            } catch (err) {
                const msg = err instanceof Error ? err.message : String(err);
                const isSchemaMigration = /migration|schema|Incompatible/i.test(msg);
                if (isSchemaMigration) {
                    logger.debug('snapshot-service', 'ℹ️ Schema migration warning (non-critical)', { error: msg });
                } else {
                    logger.warn('snapshot-service', '⚠️ Unexpected loadSnapshot error', { error: msg });
                }
            }

            setLoadingState({ status: 'ready', error: '' });
        } catch (error) {
            logger.error('snapshot-service', '❌ Failed to load snapshot', {
                error: error instanceof Error ? error.message : 'Unknown error',
            });
            setLoadingState({
                status: 'error',
                error: error instanceof Error ? error.message : 'Failed to load snapshot',
            });
        }
    }

    static async saveNodeSnapshotToDatabase(
        nodePath: string,
        accessToken: string,
        store: TLStore
    ): Promise<void> {
        try {
            logger.info('snapshot-service', '💾 Saving snapshot to Storage', { path: nodePath });
            const snapshot = getSnapshot(store);
            await storagePut(nodePath, accessToken, snapshot);
            logger.debug('snapshot-service', '✅ Snapshot saved successfully');
        } catch (error) {
            logger.error('snapshot-service', '❌ Failed to save snapshot', {
                error: error instanceof Error ? error.message : 'Unknown error',
            });
            throw error;
        }
    }

    private async saveCurrentSnapshot(nodePath: string): Promise<void> {
        if (!this.currentNodePath || this.currentNodePath !== nodePath) return;
        if (!this._accessToken) {
            logger.debug('snapshot-service', '⚠️ No access token — snapshot save skipped');
            return;
        }
        try {
            this.isSaving = true;
            await NavigationSnapshotService.saveNodeSnapshotToDatabase(nodePath, this._accessToken, this.store);
            logger.debug('snapshot-service', '✅ Saved navigation snapshot', { nodePath });
        } catch (error) {
            logger.error('snapshot-service', '❌ Failed to save navigation snapshot', {
                error: error instanceof Error ? error.message : 'Unknown error',
                nodePath,
            });
            throw error;
        } finally {
            this.isSaving = false;
        }
    }

    private async loadSnapshotForNode(node: { node_storage_path: string }): Promise<void> {
        if (!this._accessToken) {
            logger.debug('snapshot-service', '⚠️ No access token — snapshot load skipped');
            return;
        }
        try {
            this.isLoading = true;
            await NavigationSnapshotService.loadNodeSnapshotFromDatabase(
                node.node_storage_path,
                this._accessToken,
                this.store,
                (state: LoadingState) => {
                    if (state.status === 'ready') {
                        this.currentNodePath = node.node_storage_path;
                    }
                },
                undefined,
                this.editor || undefined
            );
        } finally {
            this.isLoading = false;
        }
    }

    async handleNavigationStart(fromNode: { node_storage_path: string } | null, toNode: { node_storage_path: string } | null): Promise<void> {
        if (!toNode) return;
        if (this.debounceTimeout) clearTimeout(this.debounceTimeout);
        return new Promise((resolve) => {
            this.debounceTimeout = setTimeout(async () => {
                try {
                    await this.executeNavigation(fromNode, toNode);
                    resolve();
                } catch (error) {
                    logger.error('snapshot-service', '❌ Navigation failed', error);
                    throw error;
                }
            }, 100);
        });
    }

    private async executeNavigation(fromNode: { node_storage_path: string } | null, toNode: { node_storage_path: string }): Promise<void> {
        if (this.isSaving || this.isLoading) {
            this.pendingOperation = {
                save: fromNode?.node_storage_path,
                load: toNode.node_storage_path,
            };
            return;
        }

        this.currentNodePath = null;

        if (toNode.node_storage_path) {
            await this.loadSnapshotForNode(toNode);
        }

        if (this.pendingOperation) {
            const op = this.pendingOperation;
            this.pendingOperation = null;
            await this.handleNavigationStart(
                op.save ? { node_storage_path: op.save } : null,
                op.load ? { node_storage_path: op.load } : null
            );
        }
    }

    setAutoSave(enabled: boolean): void {
        this.isAutoSaveEnabled = enabled;
    }

    setCurrentNodePath(path: string): void {
        this.currentNodePath = path;
    }

    getCurrentNodePath(): string | null {
        return this.currentNodePath;
    }

    async forceSaveCurrentNode(): Promise<void> {
        if (this.currentNodePath) {
            await this.saveCurrentSnapshot(this.currentNodePath);
        }
    }

    clearCurrentNode(): void {
        this.currentNodePath = null;
        this.store.clear();
    }
}
