import path from 'node:path';
import { ListObjectsV2Command } from '@aws-sdk/client-s3';

/* eslint-disable no-await-in-loop */
import {
    config, isFileExists, logger, pgClients, eventStream, downloadFile, s3Client,
} from '@opengis/fastify-table/utils.js';

import mapnik from '../../../plugins/mapnik/funcs/mapnik.js';

const { UploadRaster, GetRasterStatus } = mapnik();

const CHUNK_SIZE = 1024 * 1024; // 1 MB per chunk

function sequence(files, data, fn) {
    return files.reduce(
        (promise, relpath) => promise.then(() => fn({
            ...data,
            relpath, // allow upload multiple to one directory for vrt
        })),
        Promise.resolve(),
    );
}

async function uploadRasterFile({
    id, prefix, relpath: relpathOriginal, callback = () => { },
}) {
    const metadata = await isFileExists(relpathOriginal);
    const relpath = relpathOriginal.replace(config.folder, '').replace(prefix, '');

    let offset = 0;

    try {
        const uploadStatus = await GetRasterStatus({ path: relpath, md5: true });

        if (uploadStatus.finished) {
            callback(`Already uploaded: ${relpath}`);
            return { message: 'already uploaded', status: 200 };
        }

        if (!metadata || !metadata.ContentLength) {
            callback(`File not found at s3: ${relpath}`);
            return { error: `File not found at s3: ${relpath}`, code: 404 };
        }

        offset = uploadStatus.exists ? +uploadStatus.size : 0;

        if (offset === metadata.ContentLength) {
            callback(`Already uploaded: ${relpath}`);
            return { message: 'already uploaded', status: 200 };
        }

        while (offset < metadata.ContentLength) {
            const end = Math.min(offset + CHUNK_SIZE - 1, metadata.ContentLength - 1);
            const Range = `bytes=${offset}-${end}`;

            callback(`Uploading chunk ${(end / 1024 / 1024).toFixed(0)}/${(metadata.ContentLength / 1024 / 1024).toFixed(0)} MB...`);

            const chunk = await downloadFile(relpathOriginal, { Range, fallback: false });
            // const { Body: chunk } = await s3Client.send(new GetObjectCommand({ Bucket, Key, Range }));
            const bufferPart = chunk ? await chunk.transformToByteArray() : [];

            if (!bufferPart.length) {
                callback(`Server error: file not found / zero bytes: ${relpath}`);
                break;
            }

            const result = await UploadRaster({
                path: relpath,
                filesize: metadata.ContentLength,
                data: bufferPart,
                chunked: true,
            });

            if (result.finished) {
                callback(`Upload completed: ${relpath}`);
                break;
            }

            if (!result.uploaded) {
                callback(`Chunked upload error: ${relpath}`);
                break;
            }

            offset = +result.uploaded;
        }

        return null;
    }
    catch (err) {
        logger.file('mapnik/upload/error', { error: err.toString(), stack: err.stack });
        callback(config.local ? err.toString() : `Upload error: ${relpath}`);
        return null;
    }
}

export default async function uploadRaster({
    pg = pgClients.client, params,
}, reply) {
    if (!UploadRaster || !GetRasterStatus) {
        return reply.status(400).send({ error: 'mapnik server address needed', code: 400 });
    }

    const { id } = params;

    if (!id) {
        return reply.status(400).send({ error: 'not enough params: id', code: 400 });
    }

    const data = pg.pk?.['gis.rasters']
        ? await pg.query('select raster_id as id, source_path from gis.rasters where raster_id=$1::text', [id])
            .then(el => el.rows?.[0])
        : null;

    if (!data?.id) {
        return reply.status(404).send({ error: 'raster not found', code: 404 });
    }

    if (!data.source_path) {
        return reply.status(400).send({ error: 'raster source_path not set', code: 400 });
    }

    const prefix = '/map/raster';
    const relpath = `${prefix}/${data.source_path}`;

    if (!path.extname(relpath)) {
        const metadata = await s3Client.send(new ListObjectsV2Command({
            Bucket: config.s3?.containerName || 'work',
            Prefix: path.join(config.folder, relpath).replace(/\\/g, '/'),
        }));
        if (!metadata.Contents?.length) {
            return reply.status(404).send({
                error: 'invalid: raster source_path: not found / empty directory',
                code: 404,
            });
        }

        // list rasters, skip preview subdir content, skip folders from s3 directory name of which only starts with same characters, but does not exactly match
        const files = metadata.Contents.map(file => file.Key).filter((file) => path.basename(path.dirname(file)) === path.basename(relpath) && !path.basename(path.dirname(file)).startsWith('preview') && path.extname(file).toLowerCase() === '.tif');

        if (!files.length) {
            return reply.status(400).send({
                error: 'invalid: raster source_path: no raster files at directory',
                code: 400,
            });
        }
        const callback = eventStream(reply);
        await sequence(files, { id, prefix, callback }, uploadRasterFile);
        return callback('finish', 1);
    }

    const callback = eventStream(reply);
    await uploadRasterFile({
        id, prefix, callback, relpath,
    });
    return callback('finish', 1);
}
