/**
 * AtlasService
 * Handles atlas extension workflows such as storing user-contributed images and
 * registering synced image metadata imported from update bundles.
 */
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const USER_IMAGES_DIR = path.join(__dirname, '..', 'public', 'user_added_images');

const hashBuffer = (buffer) => crypto.createHash('sha256').update(buffer).digest('hex');
const toAbsoluteUserImagePath = (imagePath) => path.join(USER_IMAGES_DIR, path.basename(String(imagePath || '')));

class AtlasService {
    constructor(pool) {
        this.pool = pool;
    }

    resolveStoredImageHash(row) {
        if (row.image_hash) return row.image_hash;

        const absolutePath = toAbsoluteUserImagePath(row.image_path);
        if (!fs.existsSync(absolutePath)) return null;

        return hashBuffer(fs.readFileSync(absolutePath));
    }

    async backfillImageHashes(client, rows) {
        for (const row of rows) {
            if (!row.image_hash && row.resolvedImageHash) {
                await client.query(
                    `UPDATE user_contributed_images
                     SET image_hash = $2
                     WHERE id = $1`,
                    [row.id, row.resolvedImageHash]
                );
            }
        }
    }

    async upsertUserImageRecord(client, { optionId, imagePath, isDrawing, originalFilename, imageHash }) {
        const existingRes = await client.query(
            `SELECT id, image_path, image_hash, original_filename
             FROM user_contributed_images
             WHERE option_id = $1
               AND is_drawing = $2
             ORDER BY created_at DESC, id DESC`,
            [optionId, isDrawing]
        );

        const existingRows = existingRes.rows.map((row) => ({
            ...row,
            resolvedImageHash: this.resolveStoredImageHash(row),
        }));

        await this.backfillImageHashes(client, existingRows);

        if (imageHash) {
            const contentMatches = existingRows.filter((row) => row.resolvedImageHash === imageHash);

            if (contentMatches.length > 0) {
                const [primaryRecord, ...duplicateRecords] = contentMatches;

                if (duplicateRecords.length > 0) {
                    await client.query(
                        'DELETE FROM user_contributed_images WHERE id = ANY($1::int[])',
                        [duplicateRecords.map((row) => row.id)]
                    );
                }

                return {
                    imagePath: primaryRecord.image_path,
                    replacedImagePaths: [imagePath, ...duplicateRecords.map((row) => row.image_path)]
                        .filter((storedPath) => storedPath && storedPath !== primaryRecord.image_path),
                    skippedDuplicate: true,
                };
            }
        }

        const filenameMatches = existingRows.filter(
            (row) => String(row.original_filename || '') === String(originalFilename || '')
        );

        if (filenameMatches.length === 0) {
            await client.query(
                `INSERT INTO user_contributed_images (option_id, image_path, is_drawing, original_filename, image_hash)
                 VALUES ($1, $2, $3, $4, $5)`,
                [optionId, imagePath, isDrawing, originalFilename, imageHash || null]
            );
            return { imagePath, replacedImagePaths: [], skippedDuplicate: false };
        }

        const [primaryRecord, ...duplicateRecords] = filenameMatches;
        const replacedImagePaths = [primaryRecord.image_path, ...duplicateRecords.map((row) => row.image_path)]
            .filter((storedPath) => storedPath && storedPath !== imagePath);

        await client.query(
            `UPDATE user_contributed_images
             SET image_path = $2,
                 original_filename = $3,
                 image_hash = $4,
                 created_at = CURRENT_TIMESTAMP
             WHERE id = $1`,
            [primaryRecord.id, imagePath, originalFilename, imageHash || null]
        );

        if (duplicateRecords.length > 0) {
            await client.query(
                'DELETE FROM user_contributed_images WHERE id = ANY($1::int[])',
                [duplicateRecords.map((row) => row.id)]
            );
        }

        return { imagePath, replacedImagePaths, skippedDuplicate: false };
    }

    async addUserImage({ traitId, optionLabel, optionLabelCs, optionLabelEn, imageFile, isDrawing, imageHash }) {
        const client = await this.pool.connect();
        try {
            await client.query('BEGIN');

            const preferredLabel = optionLabel || optionLabelCs || optionLabelEn;
            const alternateLabel = optionLabelEn && optionLabelEn !== preferredLabel
                ? optionLabelEn
                : optionLabelCs && optionLabelCs !== preferredLabel
                    ? optionLabelCs
                    : null;
            const res = await client.query(
                `SELECT id
                 FROM trait_options
                 WHERE trait_id = $1
                   AND (
                       label_cs = $2
                       OR label_en = $2
                                             OR ($3::text IS NOT NULL AND (label_cs = $3::text OR label_en = $3::text))
                   )
                 LIMIT 1`,
                [traitId, preferredLabel, alternateLabel]
            );
            const optionId = res.rows[0]?.id;
            if (!optionId) throw new Error("Varianta nenalezena.");

            const imagePath = `/public/user_added_images/${imageFile.filename}`;
            const result = await this.upsertUserImageRecord(client, {
                optionId,
                imagePath,
                isDrawing,
                originalFilename: imageFile.originalname,
                imageHash,
            });

            await client.query('COMMIT');
            return result;
        } catch (e) {
            await client.query('ROLLBACK');
            throw e;
        } finally {
            client.release();
        }
    }

    async registerUserImageFromUpdate(client, update) {
        const preferredLabel = update.option_label_cs || update.option_label_en;
        const alternateLabel = update.option_label_en && update.option_label_en !== preferredLabel
            ? update.option_label_en
            : null;
        const optionRes = await client.query(
            `SELECT id
             FROM trait_options
             WHERE trait_id = $1
               AND (
                   label_cs = $2
                   OR label_en = $2
                                     OR ($3::text IS NOT NULL AND (label_cs = $3::text OR label_en = $3::text))
               )
             LIMIT 1`,
            [update.trait_id, preferredLabel, alternateLabel]
        );

        if (optionRes.rows.length > 0) {
            const optionId = optionRes.rows[0].id;
            const imagePath = `/public/user_added_images/${update.filename}`;

            return await this.upsertUserImageRecord(client, {
                optionId,
                imagePath,
                isDrawing: update.is_drawing,
                originalFilename: update.original_filename || update.filename,
                imageHash: update.imageHash || null,
            });
        }

        return null;
    }
}

module.exports = AtlasService;