const path = require('path');
const fs = require('fs');

const { WebCache, VersionResolveError } = require('./WebCache');
const {
    getIntegrityEntry,
    verifyWebCacheIntegrity,
} = require('./WebCacheIntegrity');

/**
 * LocalWebCache - Fetches a WhatsApp Web version from a local file store
 * @param {object} options - options
 * @param {string} options.path - Path to the directory where cached versions are saved, default is: "./.wwebjs_cache/"
 * @param {boolean} options.strict - If true, will throw an error if the requested version can't be fetched. Defaults to true.
 * @param {boolean} options.readOnly - When true, cache misses are not persisted.
 * @param {Record<string, {sha256: string, sizeBytes: number}>} options.integrity - Optional immutable artifact manifest.
 */
class LocalWebCache extends WebCache {
    constructor(options = {}) {
        super();

        this.path = options.path || './.wwebjs_cache/';
        this.strict = options.strict !== false;
        this.readOnly = options.readOnly === true;
        this.integrity = options.integrity;
    }

    getFilePath(version) {
        const cachePath = path.resolve(this.path);
        const filePath = path.resolve(cachePath, `${version}.html`);
        const relativePath = path.relative(cachePath, filePath);
        if (
            !relativePath ||
            relativePath.startsWith(`..${path.sep}`) ||
            path.isAbsolute(relativePath)
        ) {
            throw new VersionResolveError(
                `Invalid WhatsApp Web version ${version}`,
            );
        }
        return { cachePath, filePath };
    }

    async resolve(version) {
        const { filePath } = this.getFilePath(version);
        let content;
        try {
            content = fs.readFileSync(filePath);
        } catch (ignoredError) {
            if (this.strict)
                throw new VersionResolveError(
                    `Couldn't load version ${version} from the cache`,
                );
            return null;
        }

        verifyWebCacheIntegrity(content, version, this.integrity);
        return content.toString('utf8');
    }

    async persist(indexHtml, version) {
        if (this.readOnly || getIntegrityEntry(this.integrity, version)) return;
        const { cachePath, filePath } = this.getFilePath(version);
        fs.mkdirSync(cachePath, { recursive: true });
        try {
            if (fs.lstatSync(filePath).isSymbolicLink()) {
                throw new VersionResolveError(
                    `Refusing to persist WhatsApp Web version ${version} through a symbolic link`,
                );
            }
        } catch (error) {
            if (error instanceof VersionResolveError) throw error;
            if (error?.code !== 'ENOENT') throw error;
        }

        const noFollow = fs.constants.O_NOFOLLOW || 0;
        let descriptor;
        try {
            descriptor = fs.openSync(
                filePath,
                fs.constants.O_WRONLY |
                    fs.constants.O_CREAT |
                    fs.constants.O_TRUNC |
                    noFollow,
                0o600,
            );
        } catch (error) {
            if (error?.code === 'ELOOP') {
                throw new VersionResolveError(
                    `Refusing to persist WhatsApp Web version ${version} through a symbolic link`,
                );
            }
            throw error;
        }
        try {
            fs.writeFileSync(descriptor, indexHtml);
        } finally {
            fs.closeSync(descriptor);
        }
    }
}

module.exports = LocalWebCache;