import axios from 'axios';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';dayjs.extend(utc);
import { Platform } from 'react-native';

import * as SQLite from 'expo-sqlite';

import * as FileSystem from 'expo-file-system';

import {blobToBase64} from '@/constants/module/file_manager';
import Storage from '@/constants/module/storage';

// Most of the code here is generated by A.I with some edit and tweak.
// I'm lazy to sit and implement entire thing :)
// If you see any delulu, let me know.

const DATABASE_NAME = 'ImageCacheDB';
const MAX_ROW = 50;
const MAX_AGE = 3; // in days


class ImageStorage_Web {
    private static db: IDBDatabase;

    // Initialize the database
    private static async initDB(): Promise<IDBDatabase> {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(DATABASE_NAME, 1);

            request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
                const db = (event.target as IDBOpenDBRequest).result;
                if (!db.objectStoreNames.contains('images')) {
                    db.createObjectStore('images', { keyPath: 'link' });
                }
            };

            request.onsuccess = () => {
                resolve(request.result);
            };

            request.onerror = () => {
                console.log('Error initializing database:', request.error);
                reject(request.error);
            };
        });
    }

    // Get the database instance
    private static async getDB(): Promise<IDBDatabase> {
        if (!this.db) {
            this.db = await this.initDB();
        }
        return this.db;
    }

    // Store image data
    static async store(link: string, data: Blob): Promise<void> {
        const db = await this.getDB();
        const transaction = db.transaction('images', 'readwrite');
        const store = transaction.objectStore('images');
        const timestamp = dayjs().unix();

        store.put({ link, data, timestamp });

        transaction.onerror = () => {
            console.log('Error storing image:', transaction.error);
        };
    }

    // Get image data
    static async get(setShowCloudflareTurnstile:any, link: string, signal: AbortSignal): Promise<Object | null> {
        const db = await this.getDB();
        const transaction = db.transaction('images', 'readonly');
        const store = transaction.objectStore('images');

        return new Promise(async (resolve, reject) => {
            const request = store.get(link);

            request.onsuccess = async () => {
                const result = request.result;

                if (result) {
                    resolve({type:"blob",data:result.data});
                } else {
                    const countRequest = store.count();

                    countRequest.onsuccess = async () => {
                        if (countRequest.result >= MAX_ROW) {
                            await this.removeOldest();
                        }

                        try {
                            axios.get(link, { 
                                responseType: 'blob', 
                                timeout: 60000, 
                                signal: signal,
                                headers: {
                                    'X-CLOUDFLARE-TURNSTILE-TOKEN': await Storage.get("cloudflare-turnstile-token")
                                }, 
                            }).then(async (response) => {
                                const data = response.data;
                                await this.store(link, data);
                                await this.removeOldImages()
                                resolve({type:"blob",data:data});
                            }).catch((error) => {
                                if (error.status === 511) setShowCloudflareTurnstile(true)
                                console.log(error)
                                resolve({type:"error",data:error})
                            });
                            
                        } catch (error) {
                            console.log('Error fetching image:', error);
                            resolve({type:"error",data:error})
                        }
                    };

                    countRequest.onerror = () => {
                        console.log('Error counting images:', countRequest.error);
                        reject(countRequest.error);
                    };
                }
            };

            request.onerror = () => {
                console.log('Error getting image:', request.error);
                reject(request.error);
            };
        });
    }

    // Remove image data
    static async remove(link: string): Promise<void> {
        const db = await this.getDB();
        const transaction = db.transaction('images', 'readwrite');
        const store = transaction.objectStore('images');

        store.delete(link);

        transaction.onerror = () => {
            console.log('Error removing image:', transaction.error);
        };
    }

    // Remove the oldest image data
    private static async removeOldest(): Promise<void> {
        const db = await this.getDB();
        const transaction = db.transaction('images', 'readwrite');
        const store = transaction.objectStore('images');
        const request = store.openCursor();

        request.onsuccess = () => {
            const cursor = request.result;
            if (cursor) {
                store.delete(cursor.key);
                cursor.continue();
            }
        };

        transaction.onerror = () => {
            console.log('Error removing oldest image:', transaction.error);
        };
    }

    // Remove images older than MAX_AGE days
    static async removeOldImages(): Promise<void> {
        const db = await this.getDB();
        const transaction = db.transaction('images', 'readwrite');
        const store = transaction.objectStore('images');
        const request = store.openCursor();

        request.onsuccess = () => {
            const cursor = request.result;
            if (cursor) {
                const record = cursor.value;
                const age = dayjs().diff(dayjs.unix(record.timestamp), 'day');
                if (age > MAX_AGE) {
                    store.delete(cursor.key);
                }
                cursor.continue();
            }
        };

        transaction.onerror = () => {
            console.log('Error removing old images:', transaction.error);
        };
    }
}



class ImageStorage_Native {
    private DATABASE:any

    constructor() {
        this.DATABASE = new Promise(async (resolve, reject) => {
            const _DATABASE = await SQLite.openDatabaseAsync(DATABASE_NAME)
            await _DATABASE.runAsync(`CREATE TABLE IF NOT EXISTS images (
                link TEXT PRIMARY KEY NOT NULL,
                file_path TEXT NOT NULL,
                timestamp INTEGER NOT NULL
            );`)
            resolve(_DATABASE)
        })
    }


    public async store(link: string, file_path: string): Promise<void> {
        const db = await this.DATABASE
        const timestamp = dayjs().unix();
        
        await db.runAsync('INSERT OR REPLACE INTO images (link, file_path, timestamp) VALUES (?, ?, ?);',link, file_path, timestamp)
    }

    public async get(setShowCloudflareTurnstile:any,link: string, signal: AbortSignal) {
        return new Promise(async (resolve, reject) => {
            try{
                const db = await this.DATABASE

                // Remove all unmatched image in sqlite and local
                const file_path_list = (await db.getAllAsync('SELECT file_path FROM images')).map((item:any) => item.file_path);
                const dir_path = FileSystem.cacheDirectory + 'ComicMTL/'+ 'cover/';
                const dirInfo = await FileSystem.getInfoAsync(dir_path);
                if (!dirInfo.exists) await FileSystem.makeDirectoryAsync(dir_path, { intermediates: true });
                
                const local_file_path_list = (await FileSystem.readDirectoryAsync(dir_path)).map(file => dir_path + file);;
                const result_list = local_file_path_list.filter(item => !file_path_list.includes(item));
                for (const file_path of result_list) {
                    try {
                        // Delete the file
                        await FileSystem.deleteAsync(file_path, { idempotent: true });

                    } catch (error) {
                        resolve({type:"error",data:error})
                        console.log('#0 Error deleting file from cache:', error);
                    }
                }
                // Check if image link exists in sqlite and filesystem
                const result = await db.getFirstAsync('SELECT * FROM images WHERE link = ?;',link)
                const local_exist = result ? (await FileSystem.getInfoAsync(result.file_path)).exists : false
                if (result && local_exist) {
                    // Image link exists, return the data
                    resolve({type:"file_path",data:result.file_path})
                    
                }else{
                    const result = await db.getFirstAsync('SELECT COUNT(*) as count FROM images;')
                    if (result.count >= MAX_ROW) {
                        const result =await db.getFirstAsync('SELECT * FROM images WHERE timestamp = (SELECT MIN(timestamp) FROM images);')
                        if (result) {
                            try {
                                // Delete the file
                                const filePath = result.file_path;
                                await FileSystem.deleteAsync(filePath, { idempotent: true });
        
                            } catch (error) {
                                resolve({type:"error",data:error})
                                console.log('#1 Error deleting file from cache:', error);
                            }
                            await db.runAsync('DELETE FROM images WHERE timestamp = (SELECT MIN(timestamp) FROM images);')
                        }
                        
                    }
                    axios.get(link, { 
                        responseType: 'blob', 
                        timeout: 60000, 
                        signal: signal,
                        headers: {
                            'X-CLOUDFLARE-TURNSTILE-TOKEN': await Storage.get("cloudflare-turnstile-token")
                        }, 
                    }).then(async (response) => {


                    
                        const filename = response.headers['content-disposition'].match(/filename="([^"]+)"/)[1]
                        const base64:string = await blobToBase64(response.data);
                        

                        const dir_path = FileSystem.cacheDirectory + "ComicMTL/" + "cover/"
                        const dirInfo = await FileSystem.getInfoAsync(dir_path);
                        if (!dirInfo.exists) {
                            await FileSystem.makeDirectoryAsync(dir_path, { intermediates: true });
                        }
                        const file_path = dir_path + filename;
                        await FileSystem.writeAsStringAsync(file_path, base64.split(',')[1], {
                            encoding: FileSystem.EncodingType.Base64,
                        });
                        await this.store(link, file_path);

                        // DELETE images older than MAX_AGE days
                        const thresholdDate = dayjs().subtract(MAX_AGE * 24 * 60 * 60, 'second').unix();
                        const rows = await db.getAllAsync('SELECT * FROM images WHERE timestamp < ?;',thresholdDate);
                        for (const row of rows) {
                            const row_file_path = row.file_path;
                            try {
                                // Delete the file
                                await FileSystem.deleteAsync(row_file_path, { idempotent: true });
                                await db.runAsync('DELETE FROM images WHERE link = ?;',row.link);

                            } catch (error) {
                                resolve({type:"error",data:error})
                                console.log('#2 Error deleting file from cache:', error);
                            }
                        }
                        resolve({type:"file_path",data:file_path})
                    }).catch((error) => {
                        resolve({type:"error",data:error})
                        if (error.status === 511) setShowCloudflareTurnstile(true)
                        console.log(error)
                        
                    });
                }
            }catch(error){
                resolve({type:"error",data:error})
                console.log(error)
                
            }
        })
    }
}



var ImageCacheStorage:any
if (Platform.OS === "web"){
    ImageCacheStorage = ImageStorage_Web
}else{
    ImageCacheStorage = new ImageStorage_Native()
}

export default ImageCacheStorage;