import axios from "axios";
import shuffleArray from "shuffle-array";
import {createCanvas, loadImage} from "canvas";

const TMDB_API_BASE_URL = "https://api.themoviedb.org/3";
const TMDB_IMAGE_BASE_URL = "https://image.tmdb.org/t/p/original";

/**
 * Fetches trending movie poster paths from TMDB.
 * @param {string} apiKey - The TMDB API key.
 * @param {string} language - The language for the movie results (e.g., 'de-DE').
 * @returns {Promise<string[]>} A promise that resolves to an array of poster image URLs.
 * @throws {Error} If the API request fails or no results are found.
 */
async function fetchTrendingMoviePosters(apiKey, language) {
    const url = `${TMDB_API_BASE_URL}/trending/movie/week?language=${language}`;
    try {
        const response = await axios.get(url, {
            headers: {
                'Authorization': `Bearer ${apiKey}`,
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'User-Agent': 'Movie Posters Service (v1.0.0)'
            }
        });

        if (!response.data || !response.data.results || response.data.results.length === 0) {
            throw new Error("No trending movies found from TMDB.");
        }

        const posterUrls = response.data.results
            .filter(item => item.poster_path)
            .map(item => `${TMDB_IMAGE_BASE_URL}${item.poster_path}`);

        if (posterUrls.length === 0) {
            throw new Error("No valid poster paths found in the trending movies results.");
        }

        return posterUrls;

    } catch (error) {
        console.error("Error fetching data from TMDB:", error.message);
        if (axios.isAxiosError(error) && error.response) {
            console.error("TMDB API Response Status:", error.response.status);
            console.error("TMDB API Response Data:", error.response.data);
            throw new Error(`Failed to fetch trending movies from TMDB: ${error.response.statusText || error.message}`);
        }
        throw error;
    }
}

/**
 * Konvertiert einen RGBA8888-Pixelbuffer in einen RGB565 Little-Endian Buffer.
 * @param {Uint8ClampedArray} rgbaBuffer - Der Eingabebuffer (R, G, B, A, R, G, B, A, ...)
 * @param {number} width - Bildbreite
 * @param {number} height - Bildhöhe
 * @returns {Buffer} Ein Buffer mit RGB565-Daten (2 Bytes pro Pixel, Little Endian).
 */
function convertRgbaToRgb565(rgbaBuffer, width, height) {
    const pixelCount = width * height;
    const rgb565Buffer = Buffer.alloc(pixelCount * 2);

    for (let i = 0; i < pixelCount; i++) {
        const r = rgbaBuffer[i * 4 + 0];
        const g = rgbaBuffer[i * 4 + 1];
        const b = rgbaBuffer[i * 4 + 2];

        const r5 = (r >> 3) & 0x1F;
        const g6 = (g >> 2) & 0x3F;
        const b5 = (b >> 3) & 0x1F;

        const rgb565Value = (r5 << 11) | (g6 << 5) | b5;

        rgb565Buffer.writeUInt16LE(rgb565Value, i * 2);
    }

    return rgb565Buffer;
}

/**
 * Draws images horizontally onto the canvas, scaling and centering them.
 * @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
 * @param {string[]} imageUrls - Array of image URLs to draw.
 * @param {number} count - The number of images to draw.
 * @param {number} canvasWidth - The width of the canvas.
 * @param {number} canvasHeight - The height of the canvas.
 */
async function drawHorizontalPosters(ctx, imageUrls, count, canvasWidth, canvasHeight) {
    const imageWidth = canvasWidth / count;
    for (let i = 0; i < count; i++) {
        if (!imageUrls[i]) continue;

        try {
            const image = await loadImage(imageUrls[i]);
            const scaledHeight = image.height * (imageWidth / image.width);
            const yPos = (canvasHeight - scaledHeight) / 2;
            ctx.drawImage(image, imageWidth * i, yPos, imageWidth, scaledHeight);
        } catch (error) {
            console.error(`Error loading or drawing image ${i} (${imageUrls[i]}):`, error.message);
            ctx.fillStyle = 'grey';
            ctx.fillRect(imageWidth * i, 0, imageWidth, canvasHeight);
            ctx.fillStyle = 'white';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText('Error', imageWidth * i + imageWidth / 2, canvasHeight / 2);
        }
    }
}

/**
 * Draws images vertically onto the canvas, scaling and centering them.
 * @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
 * @param {string[]} imageUrls - Array of image URLs to draw.
 * @param {number} count - The number of images to draw.
 * @param {number} canvasWidth - The width of the canvas.
 * @param {number} canvasHeight - The height of the canvas.
 */
async function drawVerticalPosters(ctx, imageUrls, count, canvasWidth, canvasHeight) {
    const imageHeight = canvasHeight / count;
    for (let i = 0; i < count; i++) {
        if (!imageUrls[i]) continue;

        try {
            const image = await loadImage(imageUrls[i]);
            const scaledWidth = image.width * (imageHeight / image.height);
            const xPos = (canvasWidth - scaledWidth) / 2;
            ctx.drawImage(image, xPos, imageHeight * i, scaledWidth, imageHeight);
        } catch (error) {
            console.error(`Error loading or drawing image ${i} (${imageUrls[i]}):`, error.message);
            ctx.fillStyle = 'grey';
            ctx.fillRect(0, imageHeight * i, canvasWidth, imageHeight);
            ctx.fillStyle = 'white';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText('Error', canvasWidth / 2, imageHeight * i + imageHeight / 2);
        }
    }
}

/**
 * Generates the final output buffer or JSON based on requested format.
 * @param {Canvas} canvas - The canvas object.
 * @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
 * @param {'png' | 'jpeg'} imageFormat - The desired image format ('png' or 'jpeg').
 * @param {'image' | 'lvgl' | 'lvgl_binary'} outputType - The desired output type ('image' or 'lvgl').
 * @returns {{contentType: string, data: Buffer | string}} An object containing the content type and the output data (Buffer for image, string for LVGL JSON).
 * @throws {Error} If an invalid format or output type is provided.
 */
function generateOutput(canvas, ctx, imageFormat, outputType) {
    switch (outputType) {
        case 'lvgl_binary': {
            const rawRgbaData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
            const rgb565Buffer = convertRgbaToRgb565(rawRgbaData, canvas.width, canvas.height);

            return {
                contentType: 'application/octet-stream',
                contentDisposition: 'attachment; filename="image.bin"',
                data: rgb565Buffer
            }
        }

        case 'lvgl': {
            const rawRgbaData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
            const rgb565Buffer = convertRgbaToRgb565(rawRgbaData, canvas.width, canvas.height);

            const lvglColorFormat = 'LV_COLOR_FORMAT_RGB565';
            const bytesPerPixel = 2;

            const dataSize = canvas.width * canvas.height * bytesPerPixel;
            let cArrayString = `const uint8_t image_data_map[${dataSize}] = {`;

            for (let i = 0; i < rgb565Buffer.length; i++) {
                cArrayString += '0x' + rgb565Buffer[i].toString(16).padStart(2, '0').toUpperCase();

                if (i < rgb565Buffer.length - 1) {
                    cArrayString += ', ';
                }
            }
            cArrayString += '};';

            const result = {
                width: canvas.width,
                height: canvas.height,
                colorFormat: lvglColorFormat,
                pixelFormat: 'RGB565',
                data_size: dataSize,
                c_array: cArrayString
            };

            return {
                contentType: 'application/json',
                data: JSON.stringify(result, null, 2)
            };
        }

        case 'image': {
            let buffer;
            let contentType;
            switch (imageFormat) {
                case 'jpeg':
                    buffer = canvas.toBuffer('image/jpeg');
                    contentType = 'image/jpeg';
                    break;
                case 'png':
                    buffer = canvas.toBuffer('image/png');
                    contentType = 'image/png';
                    break;
                default:
                    console.warn(`Invalid image format requested: ${imageFormat}. Defaulting to PNG.`);
                    buffer = canvas.toBuffer('image/png');
                    contentType = 'image/png';
            }
            return {contentType, data: buffer};
        }

        default:
            throw new Error(`Invalid output type specified: ${outputType}`);
    }
}

/**
 * Creates a composite poster image from trending movie posters.
 *
 * @param {object} req - Express request object.
 * @param {object} res - Express response object.
 * @param {number} width - Desired width of the final poster.
 * @param {number} height - Desired height of the final poster.
 * @param {number} count - Number of movie posters to include.
 * @param {'horizontal' | 'vertical'} orientation - Layout orientation of the posters.
 * @param {boolean} shuffleImages - Whether to shuffle the fetched posters.
 * @param {string} backgroundColor - Background color for the canvas (hex code).
 * @param {'png' | 'jpeg'} format - Output image format (if output is 'image').
 * @param {string} language - Language for fetching movie posters (e.g., 'de-DE').
 * @param {'image' | 'lvgl'} output - Desired output type.
 */
export async function createPoster(req, res, width, height, count, orientation, shuffleImages, backgroundColor, format, language, output) {
    const apiKey = process.env.TMDB_API_KEY || req.header("x-api-key");
    if (!apiKey) {
        return res.status(401).send({error: "API key is missing. Provide it via x-api-key header or TMDB_API_KEY environment variable."});
    }

    try {
        let imageUrls = await fetchTrendingMoviePosters(apiKey, language);

        if (imageUrls.length < count) {
            console.warn(`Warning: Fetched ${imageUrls.length} posters, but ${count} were requested. Repeating posters.`);
            const originalUrls = [...imageUrls];
            while (imageUrls.length < count) {
                imageUrls.push(...originalUrls.slice(0, count - imageUrls.length));
            }
        }

        if (shuffleImages) {
            shuffleArray(imageUrls);
        }

        const selectedImageUrls = imageUrls.slice(0, count);

        const canvas = createCanvas(width, height);
        const ctx = canvas.getContext('2d', {pixelFormat: 'RGB24'});

        ctx.fillStyle = backgroundColor;
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        if (orientation === 'horizontal') {
            await drawHorizontalPosters(ctx, selectedImageUrls, count, canvas.width, canvas.height);
        } else if (orientation === 'vertical') {
            await drawVerticalPosters(ctx, selectedImageUrls, count, canvas.width, canvas.height);
        } else {
            throw new Error(`Invalid orientation specified: ${orientation}`);
        }

        const {contentType, data, contentDisposition} = generateOutput(canvas, ctx, format, output);
        if (contentDisposition) {
            res.setHeader('Content-Disposition', contentDisposition);
        }
        res.status(200).type(contentType).end(data);

    } catch (error) {
        console.error("Error creating poster:", error.message);
        res.status(500).send({error: `Failed to create poster: ${error.message}`});
    }
}
