import fs, { promises as fsp } from 'fs';
import path from 'path';
import { HttpStatus } from '@utils';
import { Snapshot } from '@models';
import { SnapshotDocument } from '@models/Snapshot.model';
import { ApiError, hashSync } from '@utils';
import { config } from '@config';
import log from '@logger';
import { LogOpts } from '@types';
import { UploadedFile } from 'express-fileupload';
import { ClientSession } from 'mongoose';

export interface CreateSnapshotParameters {
    name: string;
    fileData: Buffer | null;
    hashCode?: string;
}

export interface SnapshotRequestPayload {
    name?: string;
    hashCode?: string;
    files?: { file: UploadedFile };
}

const snapshotExists = async (hash?: string): Promise<SnapshotDocument | null> => {
    if (!hash) return null;
    return Snapshot.findOne({ imghash: hash });
};

export async function createSnapshot(parameters: CreateSnapshotParameters, session?: ClientSession) {
    const logOpts: LogOpts = {
        scope: 'createSnapshot',
        itemType: 'snapshot',
        msgType: 'CREATE'
    };

    const { name, fileData, hashCode } = parameters;

    const opts: Partial<SnapshotDocument> = { name };

    if (fileData === null) {
        throw new ApiError(HttpStatus.BAD_REQUEST, `cannot create the snapshot, the 'fileData' is not set, name: '${name}'`);
    }


    opts.imghash = hashCode || hashSync(fileData);
    const snapshot = new Snapshot(opts);
    const filename = `${snapshot.id}.png`;
    const imagePath = path.join(config.defaultImagesPath, filename);
    log.debug(`save screenshot for: '${name}' snapshot to: '${imagePath}'`, logOpts);
    await fsp.writeFile(imagePath, fileData);
    snapshot.filename = filename;
    await snapshot.save({ session });
    log.debug(`snapshot was saved: '${JSON.stringify(snapshot)}'`, { ...logOpts, ...{ ref: String(snapshot._id) } });
    return snapshot;
}

export async function cloneSnapshot(sourceSnapshot: SnapshotDocument, name: string, session?: ClientSession) {
    const { filename } = sourceSnapshot;
    const hashCode = sourceSnapshot.imghash;
    const newSnapshot = new Snapshot({ name, filename, imghash: hashCode });
    await newSnapshot.save({ session });
    return newSnapshot;
}

export async function isNeedFiles(
    checkParam: SnapshotRequestPayload,
    logOpts: LogOpts
): Promise<{ needFilesStatus: boolean; snapshotFoundedByHashcode: SnapshotDocument | null; }> {
    const snapshotFoundedByHashcode = await snapshotExists(checkParam.hashCode);

    if (!checkParam.hashCode && !checkParam.files) {
        log.debug('hashCode or files parameters should be present', logOpts);
        return { needFilesStatus: true, snapshotFoundedByHashcode };
    }

    if (!checkParam.files && !snapshotFoundedByHashcode) {
        log.debug(`cannot find the snapshot with hash: '${checkParam.hashCode}'`, logOpts);
        return { needFilesStatus: true, snapshotFoundedByHashcode };
    }
    return { needFilesStatus: false, snapshotFoundedByHashcode };
}

export async function prepareActualSnapshot(
    checkParam: SnapshotRequestPayload,
    snapshotFoundedByHashcode: SnapshotDocument | null,
    logOpts: LogOpts,
    session?: ClientSession
): Promise<SnapshotDocument> {
    let currentSnapshot: SnapshotDocument;
    let fileData = checkParam.files ? checkParam.files.file.data : null;

    if ((!fileData || fileData.length === 0) && checkParam.files?.file.tempFilePath) {
        fileData = await fsp.readFile(checkParam.files.file.tempFilePath);
    }

    if (snapshotFoundedByHashcode) {
        if (!snapshotFoundedByHashcode.filename) {
            throw new Error(`Snapshot filename is missing for hash: ${checkParam.hashCode}`);
        }
        const fullFilename = path.join(config.defaultImagesPath, snapshotFoundedByHashcode.filename);
        if (!fs.existsSync(fullFilename)) {
            throw new Error(`Couldn't find the baseline file: '${fullFilename}'`);
        }

        log.debug(`snapshot with such hashcode: '${checkParam.hashCode}' is already exists, will clone it`, logOpts);

        if (!checkParam.name) throw new ApiError(HttpStatus.BAD_REQUEST, `Cannot prepareActualSnapshot name is empty, hashe: ${checkParam.hashCode}`);
        currentSnapshot = await cloneSnapshot(snapshotFoundedByHashcode, checkParam.name, session);
    } else {
        log.debug(`snapshot with such hashcode: '${checkParam.hashCode}' does not exists, will create it`, logOpts);
        currentSnapshot = await createSnapshot({ name: checkParam.name!, fileData, hashCode: checkParam.hashCode }, session);
    }

    return currentSnapshot;
}

export async function getSnapshotByImgHash(hash?: string): Promise<SnapshotDocument | null> {
    return snapshotExists(hash);
}
