import React, { useMemo, useRef, useState } from 'react';
import axios from 'axios';

const MAX_BYTES = 2 * 1024 * 1024;
const ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']);

function readImage(file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onerror = () => reject(new Error('Failed to read avatar file.'));
        reader.onload = () => {
            const image = new Image();
            image.onerror = () => reject(new Error('Invalid image data.'));
            image.onload = () => resolve(image);
            image.src = String(reader.result || '');
        };
        reader.readAsDataURL(file);
    });
}

function canvasToBlob(canvas, mimeType, quality) {
    return new Promise((resolve, reject) => {
        canvas.toBlob((blob) => {
            if (!blob) {
                reject(new Error('Failed to prepare avatar preview.'));
                return;
            }
            resolve(blob);
        }, mimeType, quality);
    });
}

async function cropToSquareWebp(file) {
    const image = await readImage(file);
    const side = Math.min(image.width, image.height);
    const sourceX = Math.floor((image.width - side) / 2);
    const sourceY = Math.floor((image.height - side) / 2);

    const outputSize = Math.min(1024, side);
    const canvas = document.createElement('canvas');
    canvas.width = outputSize;
    canvas.height = outputSize;

    const context = canvas.getContext('2d', { alpha: false });
    if (!context) {
        throw new Error('Browser canvas is unavailable.');
    }

    context.fillStyle = '#ffffff';
    context.fillRect(0, 0, outputSize, outputSize);
    context.drawImage(image, sourceX, sourceY, side, side, 0, 0, outputSize, outputSize);

    const blob = await canvasToBlob(canvas, 'image/webp', 0.9);
    return new File([blob], 'avatar.webp', { type: 'image/webp' });
}

export default function AvatarUploader({ uploadUrl, initialSrc, csrfToken }) {
    const inputRef = useRef(null);
    const [error, setError] = useState('');
    const [isDragging, setIsDragging] = useState(false);
    const [isUploading, setIsUploading] = useState(false);
    const [progress, setProgress] = useState(0);
    const [avatarSrc, setAvatarSrc] = useState(initialSrc || '');

    const helperText = useMemo(() => {
        if (isUploading) {
            return `Uploading ${progress}%...`;
        }
        return 'JPG, PNG, or WebP up to 2MB. Image is center-cropped to square.';
    }, [isUploading, progress]);

    const validateClientFile = (file) => {
        if (!file) {
            throw new Error('No file selected.');
        }

        if (!ALLOWED_TYPES.has(file.type)) {
            throw new Error('Only JPG, PNG, and WebP are allowed.');
        }

        if (file.size > MAX_BYTES) {
            throw new Error('Avatar file must be 2MB or smaller.');
        }
    };

    const upload = async (file) => {
        validateClientFile(file);

        setError('');
        setProgress(0);
        setIsUploading(true);

        try {
            const squaredFile = await cropToSquareWebp(file);
            const previewUrl = URL.createObjectURL(squaredFile);
            setAvatarSrc(previewUrl);

            const formData = new FormData();
            formData.append('avatar', squaredFile);

            const response = await axios.post(uploadUrl, formData, {
                headers: {
                    'X-CSRF-TOKEN': csrfToken,
                    Accept: 'application/json',
                    'Content-Type': 'multipart/form-data',
                },
                onUploadProgress: (event) => {
                    if (!event.total) {
                        return;
                    }

                    const next = Math.round((event.loaded * 100) / event.total);
                    setProgress(next);
                },
            });

            const data = response?.data || {};
            if (typeof data.url === 'string' && data.url.length > 0) {
                setAvatarSrc(data.url);
            }
        } catch (uploadError) {
            const message = uploadError?.response?.data?.message || uploadError?.message || 'Avatar upload failed.';
            setError(message);
        } finally {
            setIsUploading(false);
        }
    };

    const onDrop = async (event) => {
        event.preventDefault();
        setIsDragging(false);

        const file = event.dataTransfer?.files?.[0];
        if (file) {
            await upload(file);
        }
    };

    const onPick = async (event) => {
        const file = event.target.files?.[0];
        if (file) {
            await upload(file);
        }

        if (event.target) {
            event.target.value = '';
        }
    };

    return (
        <div className="space-y-3">
            <p className="text-sm font-medium text-gray-900">Avatar</p>
            <div className="flex items-center gap-4">
                <img
                    src={avatarSrc || '/img/default-avatar.webp'}
                    alt="Current avatar preview"
                    width="96"
                    height="96"
                    className="h-24 w-24 rounded-full border border-gray-300 object-cover"
                    loading="lazy"
                    decoding="async"
                />

                <div
                    role="button"
                    tabIndex={0}
                    onDragOver={(event) => {
                        event.preventDefault();
                        setIsDragging(true);
                    }}
                    onDragLeave={() => setIsDragging(false)}
                    onDrop={onDrop}
                    onClick={() => inputRef.current?.click()}
                    onKeyDown={(event) => {
                        if (event.key === 'Enter' || event.key === ' ') {
                            event.preventDefault();
                            inputRef.current?.click();
                        }
                    }}
                    className={`w-full rounded-lg border-2 border-dashed p-4 text-sm transition ${isDragging ? 'border-indigo-500 bg-indigo-50' : 'border-gray-300 bg-white'}`}
                    aria-label="Upload avatar"
                >
                    <p className="text-gray-700">Drag & drop avatar here, or click to choose a file.</p>
                    <p className="mt-1 text-xs text-gray-500">{helperText}</p>
                    <input
                        ref={inputRef}
                        type="file"
                        accept="image/jpeg,image/png,image/webp"
                        className="hidden"
                        onChange={onPick}
                    />
                </div>
            </div>

            {error ? <p className="text-sm text-red-600">{error}</p> : null}
        </div>
    );
}
