import globalConfig from '@/config';
import { stringMd5, rabbitEncryptString, rabbitDecryptString } from '@/utils/crypto';

// 用一样的 key 和 iv 来加密 storage 的 key, 保证每次加密后的 storage 的 key 的结果一样，否则每次获取数据都需要把 storage 中的所有 key 解密一遍才知道哪个是我们需要的 key
const storageKeyIV = stringMd5(`${globalConfig.app.title}_${globalConfig.app.version}`).slice(0, 8);

export function encryptKey(key: string) {
    return rabbitEncryptString(key, globalConfig.security.storageEncryptionKey, storageKeyIV);
}

export function decryptKey(key: string) {
    return rabbitDecryptString(key, globalConfig.security.storageEncryptionKey, storageKeyIV);
}

export function encryptData(data: string) {
    return rabbitEncryptString(data, globalConfig.security.storageEncryptionKey);
}

export function decryptData(data: string) {
    return rabbitDecryptString(data, globalConfig.security.storageEncryptionKey);
}

export interface StorageOptions {
    /**
     * 过期时间(秒), 0 为永远不过期
     * @default 0
     */
    expire?: number;
    /**
     * 是否加密key
     * @default false
     */
    encryptKey?: boolean;
    /**
     * 是否加密数据
     * @default false
     */
    encryptData?: boolean;
}

interface StorageData<T = any> {
    data: T;
    expire: number;
}

/**
 * 是否处于可访问 localStorage 的浏览器环境。
 * 预渲染（SSG）在 Node 端执行，无 window/localStorage，此时所有 storage 操作应安全降级为空操作/返回 null，
 * 而非抛错。hydrate 后回到浏览器环境即恢复正常读写。
 */
const canUseStorage = () => typeof window !== 'undefined' && !!window.localStorage;

function shouldEncryptKey(functionOptions?: StorageOptions, instanceOptions?: StorageOptions) {
    if (globalConfig.security.enableStorageEncryption) {
        return functionOptions?.encryptKey ?? instanceOptions?.encryptKey ?? false;
    }
    return false;
}

function shouldEncryptData(funcOpts?: StorageOptions, instanceOpts?: StorageOptions) {
    if (globalConfig.security.enableStorageEncryption) {
        return funcOpts?.encryptData ?? instanceOpts?.encryptData ?? false;
    }
    return false;
}

/**
 * 创建 localStorage 工具实例
 * @param opts {StorageOptions} 本实例使用的全局配置
 * @returns
 */
export function createLocalStorage({
    expire: confExpire = 0,
    encryptKey: confEncryptKey = false,
    encryptData: confEncryptData = false,
}: StorageOptions = {}) {
    /**
     * 保存数据
     * @param key
     * @param data
     * @param opts 当传递了 opts 时，opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
     */
    function set<T = any>(key: string, data: T, opts?: StorageOptions) {
        if (!canUseStorage()) return;
        const expire = opts?.expire ?? confExpire;
        const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
        const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });

        const storageData: StorageData<T> = {
            data,
            expire: expire !== 0 ? new Date().getTime() + expire * 1000 : 0,
        };

        const json = JSON.stringify(storageData);
        const finalKey = isEncryptKey ? encryptKey(key) : key;
        const finalData = isEncryptData ? encryptData(json) : json;

        window.localStorage.setItem(finalKey, finalData);
    }

    /**
     * 读取数据
     * @param key
     * @param opts 当传递了 opts 时，opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
     * @returns
     */
    function get<T = any>(key: string, opts?: StorageOptions) {
        if (!canUseStorage()) return null;
        const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
        const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });

        const finalKey = isEncryptKey ? encryptKey(key) : key;
        let data = window.localStorage.getItem(finalKey);
        if (!data) return null;

        if (isEncryptData) {
            data = decryptData(data);
        }
        if (!data) return null;

        let storageData: StorageData | null = null;

        try {
            storageData = JSON.parse(data);
        } catch {
            // Prevent failure
        }

        if (storageData) {
            const { data, expire = 0 } = storageData;
            if (expire === 0 || expire >= Date.now()) return data as T;
        }

        return null;
    }

    /**
     * 删除数据
     * @param key
     * @param opts 当传递了 opts 时，opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
     */
    function remove(key: string, opts?: StorageOptions) {
        if (!canUseStorage()) return;
        const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
        const finalKey = isEncryptKey ? encryptKey(key) : key;
        window.localStorage.removeItem(finalKey);
    }

    /**
     * 清除 localStorage 中的所有数据。**慎用**
     */
    function clear() {
        if (!canUseStorage()) return;
        window.localStorage.clear();
    }

    return { set, get, remove, clear };
}

/**
 * 创建 sessionStorage 工具实例
 * @param opts {StorageOptions} 本实例使用的全局配置
 * @returns
 */
export function createSessionStorage({
    expire: confExpire = 0,
    encryptKey: confEncryptKey = false,
    encryptData: confEncryptData = false,
}: StorageOptions = {}) {
    /**
     * 保存数据
     * @param key
     * @param data
     * @param opts 当传递了 opts 时，opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
     */
    function set<T = any>(key: string, data: T, opts?: StorageOptions) {
        if (!canUseStorage()) return;
        const expire = opts?.expire ?? confExpire;
        const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
        const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });

        const storageData: StorageData<T> = {
            data,
            expire: expire !== 0 ? new Date().getTime() + expire * 1000 : 0,
        };

        const json = JSON.stringify(storageData);
        const finalKey = isEncryptKey ? encryptKey(key) : key;
        const finalData = isEncryptData ? encryptData(json) : json;

        window.sessionStorage.setItem(finalKey, finalData);
    }

    /**
     * 读取数据
     * @param key
     * @param opts 当传递了 opts 时，opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
     * @returns
     */
    function get<T = any>(key: string, opts?: StorageOptions) {
        if (!canUseStorage()) return null;
        const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
        const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });

        const finalKey = isEncryptKey ? encryptKey(key) : key;
        let data = window.sessionStorage.getItem(finalKey);
        if (!data) return null;

        if (isEncryptData) {
            data = decryptData(data);
        }
        if (!data) return null;

        let storageData: StorageData | null = null;

        try {
            storageData = JSON.parse(data);
        } catch {
            // Prevent failure
        }

        if (storageData) {
            const { data, expire = 0 } = storageData;
            if (expire === 0 || expire >= Date.now()) return data as T;
        }

        return null;
    }

    /**
     * 删除数据
     * @param key
     * @param opts 当传递了 opts 时，opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
     */
    function remove(key: string, opts?: StorageOptions) {
        if (!canUseStorage()) return;
        const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
        const finalKey = isEncryptKey ? encryptKey(key) : key;
        window.sessionStorage.removeItem(finalKey);
    }

    /**
     * 清除 sessionStorage 中的所有数据。**慎用**
     */
    function clear() {
        if (!canUseStorage()) return;
        window.sessionStorage.clear();
    }

    return { set, get, remove, clear };
}

export const ls = createLocalStorage({
    expire: 60 * 60 * 24 * 7,
    encryptKey: true,
    encryptData: true,
});
export const ss = createSessionStorage({
    expire: 60 * 60 * 24 * 7,
    encryptKey: true,
    encryptData: true,
});
