import type { HttpContext } from '@adonisjs/core/http';
import { StatusCodes } from 'http-status-codes';
import redis from '@adonisjs/redis/services/main';

const PREFIXES = ['von', 'van'];
const DEFAULT_SIZE = 50;
const FONT_SIZE_RATIO = 0.4;
const COLOR_LIGHTENING_PERCENT = 60;
const COLOR_DARKENING_FACTOR = 0.6;

export default class AvatarController {
    public async generateAvatar({ request, response }: HttpContext) {
        try {
            const { name, size = DEFAULT_SIZE } = request.only(['name', 'size']);
            if (!name) {
                return response.status(StatusCodes.BAD_REQUEST).json({ error: 'Name is required' });
            }

            // Build a unique cache key for the given name and size
            const cacheKey = `avatar:${name.trim().toLowerCase()}-${size}`;
            const cachedSvg = await redis.get(cacheKey);
            if (cachedSvg) {
                this.setResponseHeaders(response);
                return response.send(cachedSvg);
            }

            const initials = this.getInitials(name);
            const colors = this.generateColors(name);
            const svgContent = this.createSvg(size, colors, initials);

            // // Cache the generated avatar for future use, e.g. 1 hour expiry
            await redis.setex(cacheKey, 3600, svgContent);

            this.setResponseHeaders(response);
            return response.send(svgContent);
        } catch (error) {
            return response.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: error.message });
        }
    }

    private getInitials(name: string): string {
        const parts = name
            .trim()
            .split(' ')
            .filter((part) => part.length > 0);

        if (parts.length === 0) {
            return 'NA';
        }

        if (parts.length >= 2) {
            return this.getMultiWordInitials(parts);
        }
        return parts[0].substring(0, 2).toUpperCase();
    }

    private getMultiWordInitials(parts: string[]): string {
        const firstName = parts[0];
        const lastName = parts[parts.length - 1];
        const firstInitial = firstName.charAt(0).toUpperCase();
        const lastInitial = lastName.charAt(0).toUpperCase();

        if (PREFIXES.includes(lastName.toLowerCase()) && lastName === lastName.toUpperCase()) {
            return firstInitial + lastName.charAt(1).toUpperCase();
        }
        return firstInitial + lastInitial;
    }

    private generateColors(name: string): { background: string; text: string } {
        const baseColor = this.getColorFromName(name);
        return {
            background: this.lightenColor(baseColor, COLOR_LIGHTENING_PERCENT),
            text: this.darkenColor(baseColor),
        };
    }

    private createSvg(size: number, colors: { background: string; text: string }, initials: string): string {
        const fontSize = size * FONT_SIZE_RATIO;
        return `
            <svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
              <rect width="100%" height="100%" fill="#${colors.background}"/>
              <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-weight="bold" font-family="Arial, sans-serif" font-size="${fontSize}" fill="#${colors.text}">${initials}</text>
            </svg>
          `;
    }

    private setResponseHeaders(response: HttpContext['response']): void {
        response.header('Content-type', 'image/svg+xml');
        response.header('Cache-Control', 'no-cache');
        response.header('Pragma', 'no-cache');
        response.header('Expires', '0');
    }

    private getColorFromName(name: string): string {
        let hash = 0;
        for (let i = 0; i < name.length; i++) {
            hash = name.charCodeAt(i) + ((hash << 5) - hash);
        }

        const colorParts = [];
        for (let i = 0; i < 3; i++) {
            const value = (hash >> (i * 8)) & 0xff;
            colorParts.push(value.toString(16).padStart(2, '0'));
        }
        return colorParts.join('');
    }

    private lightenColor(hexColor: string, percent: number): string {
        const r = parseInt(hexColor.substring(0, 2), 16);
        const g = parseInt(hexColor.substring(2, 4), 16);
        const b = parseInt(hexColor.substring(4, 6), 16);

        const lightenValue = (value: number) => Math.min(255, Math.floor((value * (100 + percent)) / 100));

        const newR = lightenValue(r);
        const newG = lightenValue(g);
        const newB = lightenValue(b);

        return ((newR << 16) | (newG << 8) | newB).toString(16).padStart(6, '0');
    }

    private darkenColor(hexColor: string): string {
        const r = parseInt(hexColor.slice(0, 2), 16);
        const g = parseInt(hexColor.slice(2, 4), 16);
        const b = parseInt(hexColor.slice(4, 6), 16);

        const darkenValue = (value: number) => Math.round(value * COLOR_DARKENING_FACTOR);

        const darkerR = darkenValue(r);
        const darkerG = darkenValue(g);
        const darkerB = darkenValue(b);

        return ((darkerR << 16) + (darkerG << 8) + darkerB).toString(16).padStart(6, '0');
    }
}
