import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
import { writeFile, unlink } from 'fs/promises';
import path from 'path';
import { parseBuffer } from 'music-metadata';
import { isDuplicateSong } from '@/lib/fuzzyMatch';
import { getStaffContext, requireStaffAuth, StaffContext } from '@/lib/auth';

const prisma = new PrismaClient();

async function getCuratorAssignments(curatorId: number) {
    const [genres, specials] = await Promise.all([
        prisma.curatorGenre.findMany({
            where: { curatorId },
            select: { genreId: true },
        }),
        prisma.curatorSpecial.findMany({
            where: { curatorId },
            select: { specialId: true },
        }),
    ]);

    return {
        genreIds: new Set(genres.map(g => g.genreId)),
        specialIds: new Set(specials.map(s => s.specialId)),
    };
}

function curatorCanEditSong(context: StaffContext, song: any, assignments: { genreIds: Set<number>; specialIds: Set<number> }) {
    if (context.role === 'admin') return true;

    const songGenreIds = (song.genres || []).map((g: any) => g.id);
    // `song.specials` kann je nach Context entweder ein Array von
    // - `Special`        (mit `id`)
    // - `SpecialSong`    (mit `specialId`)
    // - `SpecialSong`    (mit Relation `special.id`)
    // sein. Wir normalisieren hier auf reine Zahlen-IDs.
    // WICHTIG: Bei SpecialSong-Objekten ist s.id die SpecialSong-ID, nicht die Special-ID!
    // Daher zuerst specialId oder special.id prüfen.
    const songSpecialIds = (song.specials || [])
        .map((s: any) => {
            // Priorität: specialId oder special.id (die tatsächliche Special-ID)
            if (s?.specialId != null) return s.specialId;
            if (s?.special?.id != null) return s.special.id;
            // Nur wenn es direkt ein Special-Objekt ist (nicht SpecialSong), verwende s.id
            if (s?.id != null && s?.specialId == null && s?.special == null) return s.id;
            return undefined;
        })
        .filter((id: any): id is number => typeof id === 'number');

    // Songs ohne Genres/Specials sind für Kuratoren generell editierbar
    if (songGenreIds.length === 0 && songSpecialIds.length === 0) {
        return true;
    }

    const hasGenre = songGenreIds.some((id: number) => assignments.genreIds.has(id));
    const hasSpecial = songSpecialIds.some((id: number) => assignments.specialIds.has(id));

    return hasGenre || hasSpecial;
}

function curatorCanDeleteSong(context: StaffContext, song: any, assignments: { genreIds: Set<number>; specialIds: Set<number> }) {
    if (context.role === 'admin') return true;

    const songGenreIds = (song.genres || []).map((g: any) => g.id);
    // WICHTIG: Bei SpecialSong-Objekten ist s.id die SpecialSong-ID, nicht die Special-ID!
    // Daher zuerst specialId oder special.id prüfen.
    const songSpecialIds = (song.specials || [])
        .map((s: any) => {
            // Priorität: specialId oder special.id (die tatsächliche Special-ID)
            if (s?.specialId != null) return s.specialId;
            if (s?.special?.id != null) return s.special.id;
            // Nur wenn es direkt ein Special-Objekt ist (nicht SpecialSong), verwende s.id
            if (s?.id != null && s?.specialId == null && s?.special == null) return s.id;
            return undefined;
        })
        .filter((id: any): id is number => typeof id === 'number');

    const allGenresAllowed = songGenreIds.every((id: number) => assignments.genreIds.has(id));
    const allSpecialsAllowed = songSpecialIds.every((id: number) => assignments.specialIds.has(id));

    return allGenresAllowed && allSpecialsAllowed;
}

// Configure route to handle large file uploads
export const runtime = 'nodejs';
export const maxDuration = 60; // 60 seconds timeout for uploads

export async function GET(request: NextRequest) {
    // Alle Zugriffe auf die Songliste erfordern Staff-Auth (Admin oder Kurator)
    const { error, context } = await requireStaffAuth(request);
    if (error || !context) return error!;

    const songs = await prisma.song.findMany({
        orderBy: { createdAt: 'desc' },
        include: {
            puzzles: true,
            genres: true,
            specials: {
                include: {
                    special: true
                }
            },
        },
    });

    let visibleSongs = songs;

    if (context.role === 'curator') {
        const assignments = await getCuratorAssignments(context.curator.id);

        visibleSongs = songs.filter(song => {
            const songGenreIds = song.genres.map(g => g.id);
            // `song.specials` ist hier ein Array von SpecialSong mit Relation `special`.
            // Es kann theoretisch verwaiste Einträge ohne `special` geben → defensiv optional chainen.
            const songSpecialIds = song.specials
                .map(ss => ss.special?.id)
                .filter((id): id is number => typeof id === 'number');

            // Songs ohne Genres/Specials sind immer sichtbar
            if (songGenreIds.length === 0 && songSpecialIds.length === 0) {
                return true;
            }

            const hasGenre = songGenreIds.some(id => assignments.genreIds.has(id));
            const hasSpecial = songSpecialIds.some(id => assignments.specialIds.has(id));

            return hasGenre || hasSpecial;
        });
    }

    // Map to include activation count and flatten specials
    const songsWithActivations = visibleSongs.map(song => ({
        id: song.id,
        title: song.title,
        artist: song.artist,
        filename: song.filename,
        createdAt: song.createdAt,
        coverImage: song.coverImage,
        releaseYear: song.releaseYear,
        activations: song.puzzles.length,
        puzzles: song.puzzles,
        genres: song.genres,
        // Nur Specials mit existierender Relation durchreichen, um undefinierte Einträge zu vermeiden.
        specials: song.specials
            .map(ss => ss.special)
            .filter((s): s is any => !!s),
        averageRating: song.averageRating,
        ratingCount: song.ratingCount,
        excludeFromGlobal: song.excludeFromGlobal,
    }));

    return NextResponse.json(songsWithActivations);
}

export async function POST(request: Request) {
    console.log('[UPLOAD] Starting song upload request');

    // Check authentication (admin or curator)
    const { error, context } = await requireStaffAuth(request as unknown as NextRequest);
    if (error || !context) {
        console.log('[UPLOAD] Authentication failed');
        return error!;
    }

    try {
        console.log('[UPLOAD] Parsing form data...');
        const formData = await request.formData();
        const file = formData.get('file') as File;
        let title = '';
        let artist = '';
        let excludeFromGlobal = formData.get('excludeFromGlobal') === 'true';

        console.log('[UPLOAD] Received file:', file?.name, 'Size:', file?.size, 'Type:', file?.type);
        console.log('[UPLOAD] excludeFromGlobal (raw):', excludeFromGlobal);

        // Apply global playlist rules:
        // - Admin: may control the flag via form data
        // - Curator: uploads are always excluded from global by default
        if (context.role === 'curator') {
            excludeFromGlobal = true;
        }

        if (!file) {
            console.error('[UPLOAD] No file provided');
            return NextResponse.json({ error: 'No file provided' }, { status: 400 });
        }

        // Security: Validate file size (max 50MB)
        const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
        if (file.size > MAX_FILE_SIZE) {
            return NextResponse.json({
                error: `File too large. Maximum size is 50MB, got ${(file.size / 1024 / 1024).toFixed(2)}MB`
            }, { status: 400 });
        }

        // Security: Validate MIME type
        const allowedMimeTypes = ['audio/mpeg', 'audio/mp3'];
        if (!allowedMimeTypes.includes(file.type)) {
            return NextResponse.json({
                error: `Invalid file type. Expected MP3, got ${file.type}`
            }, { status: 400 });
        }

        // Security: Validate file extension
        if (!file.name.toLowerCase().endsWith('.mp3')) {
            return NextResponse.json({
                error: 'Invalid file extension. Only .mp3 files are allowed'
            }, { status: 400 });
        }

        const buffer = Buffer.from(await file.arrayBuffer());
        console.log('[UPLOAD] Buffer created, size:', buffer.length, 'bytes');

        // Validate and extract metadata from file
        let metadata;
        let releaseYear: number | null = null;
        let validationInfo = {
            isValid: true,
            hasCover: false,
            format: '',
            bitrate: 0,
            sampleRate: 0,
            duration: 0,
            codec: '',
            warnings: [] as string[],
        };

        try {
            metadata = await parseBuffer(buffer, file.type);

            // Extract basic metadata
            if (metadata.common.title) {
                title = metadata.common.title;
            }

            // Handle artist - prefer artists array if available
            if (metadata.common.artists && metadata.common.artists.length > 0) {
                // Join multiple artists with '/'
                artist = metadata.common.artists.join('/');
            } else if (metadata.common.artist) {
                artist = metadata.common.artist;
            } else if (metadata.common.albumartist) {
                // Fallback to album artist
                artist = metadata.common.albumartist;
            }

            // Try to extract release year from tags (preferred over external APIs)
            if (typeof metadata.common.year === 'number') {
                releaseYear = metadata.common.year;
            }

            // Validation info
            validationInfo.hasCover = !!metadata.common.picture?.[0];
            validationInfo.format = metadata.format.container || 'unknown';
            validationInfo.bitrate = metadata.format.bitrate || 0;
            validationInfo.sampleRate = metadata.format.sampleRate || 0;
            validationInfo.duration = metadata.format.duration || 0;
            validationInfo.codec = metadata.format.codec || 'unknown';

            // Validate format
            if (metadata.format.container !== 'MPEG') {
                validationInfo.warnings.push('File may not be a standard MP3 (MPEG container expected)');
            }

            // Check bitrate
            if (validationInfo.bitrate && validationInfo.bitrate < 96000) {
                validationInfo.warnings.push(`Low bitrate detected: ${Math.round(validationInfo.bitrate / 1000)} kbps`);
            }

            // Check sample rate
            if (validationInfo.sampleRate && ![44100, 48000].includes(validationInfo.sampleRate)) {
                validationInfo.warnings.push(`Non-standard sample rate: ${validationInfo.sampleRate} Hz (recommended: 44100 or 48000 Hz)`);
            }

            // Check duration
            if (!validationInfo.duration || validationInfo.duration < 30) {
                validationInfo.warnings.push('Audio file is very short (less than 30 seconds)');
            }

        } catch (e) {
            console.error('Failed to parse metadata:', e);
            validationInfo.isValid = false;
            validationInfo.warnings.push('Failed to parse audio metadata - file may be corrupted');
        }

        // Fallback if still missing
        if (!title) title = 'Unknown Title';
        if (!artist) artist = 'Unknown Artist';

        // Check for duplicates
        const existingSongs = await prisma.song.findMany({
            select: { id: true, title: true, artist: true, filename: true }
        });

        for (const existing of existingSongs) {
            if (isDuplicateSong(artist, title, existing.artist, existing.title)) {
                return NextResponse.json(
                    {
                        error: 'Duplicate song detected',
                        duplicate: {
                            id: existing.id,
                            title: existing.title,
                            artist: existing.artist,
                            filename: existing.filename
                        }
                    },
                    { status: 409 }
                );
            }
        }

        // Create URL-safe filename
        const originalName = file.name.replace(/\.mp3$/i, '');
        const sanitizedName = originalName
            .replace(/[^a-zA-Z0-9]/g, '-') // Replace special chars with dash
            .replace(/-+/g, '-') // Replace multiple dashes with single dash
            .replace(/^-|-$/g, ''); // Remove leading/trailing dashes

        // Warn if filename was changed
        if (originalName !== sanitizedName) {
            validationInfo.warnings.push(`Filename sanitized: "${originalName}" → "${sanitizedName}"`);
        }

        const filename = `${Date.now()}-${sanitizedName}.mp3`;
        const uploadDir = path.join(process.cwd(), 'public/uploads');

        await writeFile(path.join(uploadDir, filename), buffer);

        // Handle cover image
        let coverImage = null;
        try {
            const picture = metadata?.common.picture?.[0];

            if (picture) {
                const extension = picture.format.split('/')[1] || 'jpg';
                const coverFilename = `cover-${Date.now()}.${extension}`;
                const coverPath = path.join(process.cwd(), 'public/uploads/covers', coverFilename);

                await writeFile(coverPath, picture.data);
                coverImage = coverFilename;
            }
        } catch (e) {
            console.error('Failed to extract cover image:', e);
        }

        // Fetch release year from iTunes only if not already present from tags
        if (releaseYear == null) {
            try {
                const { getReleaseYearFromItunes } = await import('@/lib/itunes');
                const fetchedYear = await getReleaseYearFromItunes(artist, title);

                if (fetchedYear) {
                    releaseYear = fetchedYear;
                    console.log(`Fetched release year ${releaseYear} from iTunes for "${title}" by "${artist}"`);
                }
            } catch (e) {
                console.error('Failed to fetch release year:', e);
            }
        }

        const song = await prisma.song.create({
            data: {
                title,
                artist,
                filename,
                coverImage,
                releaseYear,
                excludeFromGlobal,
            },
            include: { genres: true, specials: true }
        });

        return NextResponse.json({
            song,
            validation: validationInfo,
        });
    } catch (error) {
        console.error('Error uploading song:', error);
        return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
    }
}

export async function PUT(request: Request) {
    // Check authentication (admin or curator)
    const { error, context } = await requireStaffAuth(request as unknown as NextRequest);
    if (error || !context) return error!;

    try {
        const { id, title, artist, releaseYear, genreIds, specialIds, excludeFromGlobal } = await request.json();

        if (!id || !title || !artist) {
            return NextResponse.json({ error: 'Missing fields' }, { status: 400 });
        }

        // Load current song with relations for permission checks
        const existingSong = await prisma.song.findUnique({
            where: { id: Number(id) },
            include: {
                genres: true,
                specials: {
                    include: {
                        special: true
                    }
                },
            },
        });

        if (!existingSong) {
            return NextResponse.json({ error: 'Song not found' }, { status: 404 });
        }
        
        let effectiveGenreIds = genreIds as number[] | undefined;
        let effectiveSpecialIds = specialIds as number[] | undefined;

        if (context.role === 'curator') {
            const assignments = await getCuratorAssignments(context.curator.id);

            if (!curatorCanEditSong(context, existingSong, assignments)) {
                return NextResponse.json({ error: 'Forbidden: You are not allowed to edit this song' }, { status: 403 });
            }

            // Curators may assign genres, but only within their own assignments.
            // Genres außerhalb ihres Zuständigkeitsbereichs bleiben unverändert bestehen.
            if (effectiveGenreIds !== undefined) {
                const invalidGenre = effectiveGenreIds.some(id => !assignments.genreIds.has(id));
                if (invalidGenre) {
                    return NextResponse.json(
                        { error: 'Curators may only assign their own genres' },
                        { status: 403 }
                    );
                }

                const fixedGenreIds = existingSong.genres
                    .filter(g => !assignments.genreIds.has(g.id))
                    .map(g => g.id);
                const managedGenreIds = effectiveGenreIds.filter(id => assignments.genreIds.has(id));
                effectiveGenreIds = Array.from(new Set([...fixedGenreIds, ...managedGenreIds]));
            }

            // Curators may assign specials, but only within their own assignments.
            // Specials außerhalb ihres Zuständigkeitsbereichs bleiben unverändert bestehen.
            if (effectiveSpecialIds !== undefined) {
                const invalidSpecial = effectiveSpecialIds.some(id => !assignments.specialIds.has(id));
                if (invalidSpecial) {
                    return NextResponse.json(
                        { error: 'Curators may only assign their own specials' },
                        { status: 403 }
                    );
                }

                const currentSpecials = await prisma.specialSong.findMany({
                    where: { songId: Number(id) }
                });
                const fixedSpecialIds = currentSpecials
                    .map(ss => ss.specialId)
                    .filter(sid => !assignments.specialIds.has(sid));
                const managedSpecialIds = effectiveSpecialIds.filter(id => assignments.specialIds.has(id));
                effectiveSpecialIds = Array.from(new Set([...fixedSpecialIds, ...managedSpecialIds]));
            }
        }

        const data: any = { title, artist };

        // Update releaseYear if provided (can be null to clear it)
        if (releaseYear !== undefined) {
            data.releaseYear = releaseYear;
        }

        if (excludeFromGlobal !== undefined) {
            if (context.role === 'admin') {
                data.excludeFromGlobal = excludeFromGlobal;
            } else {
                // Curators may only change the flag if they are global curators
                if (!context.curator.isGlobalCurator) {
                    return NextResponse.json(
                        { error: 'Forbidden: Only global curators or admins can change global playlist flag' },
                        { status: 403 }
                    );
                }
                data.excludeFromGlobal = excludeFromGlobal;
            }
        }

        // Wenn effectiveGenreIds definiert ist, auch leere Arrays übernehmen (löscht alle Zuordnungen).
        if (effectiveGenreIds !== undefined) {
            data.genres = {
                set: effectiveGenreIds.map((gId: number) => ({ id: gId }))
            };
        }

        // Execute all database write operations in a transaction to ensure consistency
        const updatedSong = await prisma.$transaction(async (tx) => {
            // Handle SpecialSong relations separately
            if (effectiveSpecialIds !== undefined) {
                // First, get current special assignments (within transaction)
                const currentSpecials = await tx.specialSong.findMany({
                    where: { songId: Number(id) }
                });

                const currentSpecialIds = currentSpecials.map(ss => ss.specialId);
                const newSpecialIds = effectiveSpecialIds as number[];

                // Delete removed specials
                const toDelete = currentSpecialIds.filter(sid => !newSpecialIds.includes(sid));
                if (toDelete.length > 0) {
                    await tx.specialSong.deleteMany({
                        where: {
                            songId: Number(id),
                            specialId: { in: toDelete }
                        }
                    });
                }

                // Add new specials
                const toAdd = newSpecialIds.filter(sid => !currentSpecialIds.includes(sid));
                if (toAdd.length > 0) {
                    await tx.specialSong.createMany({
                        data: toAdd.map(specialId => ({
                            songId: Number(id),
                            specialId,
                            startTime: 0
                        }))
                    });
                }
            }

            // Update song (this also handles genre relations via Prisma's set operation)
            return await tx.song.update({
                where: { id: Number(id) },
                data,
                include: {
                    genres: true,
                    specials: {
                        include: {
                            special: true
                        }
                    }
                }
            });
        });

        return NextResponse.json(updatedSong);
    } catch (error) {
        console.error('Error updating song:', error);
        return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
    }
}

export async function DELETE(request: Request) {
    // Check authentication (admin or curator)
    const { error, context } = await requireStaffAuth(request as unknown as NextRequest);
    if (error || !context) return error!;

    try {
        const { id } = await request.json();

        if (!id) {
            return NextResponse.json({ error: 'Missing id' }, { status: 400 });
        }

        // Get song to find filename and relations for permission checks
        const song = await prisma.song.findUnique({
            where: { id: Number(id) },
            include: {
                genres: true,
                specials: true,
            },
        });

        if (!song) {
            return NextResponse.json({ error: 'Song not found' }, { status: 404 });
        }

        if (context.role === 'curator') {
            const assignments = await getCuratorAssignments(context.curator.id);

            if (!curatorCanDeleteSong(context, song, assignments)) {
                return NextResponse.json(
                    { error: 'Forbidden: You are not allowed to delete this song' },
                    { status: 403 }
                );
            }
        }

        // Delete files first (outside transaction, as file system operations can't be rolled back)
        const filePath = path.join(process.cwd(), 'public/uploads', song.filename);
        try {
            await unlink(filePath);
        } catch (e) {
            console.error('Failed to delete file:', e);
            // Continue with DB deletion even if file deletion fails
        }

        // Delete cover image if exists
        if (song.coverImage) {
            const coverPath = path.join(process.cwd(), 'public/uploads/covers', song.coverImage);
            try {
                await unlink(coverPath);
            } catch (e) {
                console.error('Failed to delete cover image:', e);
            }
        }

        // Delete from database in transaction (will cascade delete related puzzles, SpecialSong, etc.)
        await prisma.$transaction(async (tx) => {
            await tx.song.delete({
                where: { id: Number(id) },
            });
        });

        return NextResponse.json({ success: true });
    } catch (error) {
        console.error('Error deleting song:', error);
        return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
    }
}
