import { NextRequest, NextResponse } from "next/server";

// Global cache for the access token to avoid refetching on every request.
// This persists in memory as long as the server (or lambda container) is alive.
let cachedToken: string | null = null;
let tokenExpiry: number = 0;

async function getAccessToken(clientId: string, clientSecret: string): Promise<string | null> {
    // Check if we have a valid cached token (with 5 minute buffer)
    if (cachedToken && Date.now() < tokenExpiry) {
        return cachedToken;
    }

    console.log("Refreshing TIDAL access token...");
    try {
        const authString = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
        const res = await fetch("https://auth.tidal.com/v1/oauth2/token", {
            method: "POST",
            headers: {
                "Authorization": `Basic ${authString}`,
                "Content-Type": "application/x-www-form-urlencoded",
            },
            body: "grant_type=client_credentials",
            cache: 'no-store' // Ensure we don't get a stale cached response from Next.js
        });

        if (!res.ok) {
            console.error("TIDAL Auth Failed:", await res.text());
            return null;
        }

        const data = await res.json();
        cachedToken = data.access_token;
        // Set expiry buffer (e.g., expires in 1 hour, we refresh after 55 mins)
        // expires_in is in seconds
        const expiresIn = data.expires_in || 3600;
        tokenExpiry = Date.now() + (expiresIn * 1000) - 300000;

        return cachedToken;
    } catch (error) {
        console.error("TIDAL Auth Exception:", error);
        return null;
    }
}

// Wrapper for TIDAL API requests that handles auth and 401 retries
async function fetchTidal(url: string, clientId: string, clientSecret: string) {
    let token = await getAccessToken(clientId, clientSecret);
    if (!token) throw new Error("Could not obtain access token");

    let res = await fetch(url, {
        headers: {
            "Authorization": `Bearer ${token}`,
            "Content-Type": "application/vnd.api+json",
        },
    });

    // If unauthorized, invalid token, or expired: clear cache and retry once
    if (res.status === 401) {
        console.warn("Got 401 from TIDAL, retrying with new token...");
        cachedToken = null;
        tokenExpiry = 0;

        token = await getAccessToken(clientId, clientSecret);
        if (!token) throw new Error("Could not refresh access token");

        res = await fetch(url, {
            headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/vnd.api+json",
            },
        });
    }

    return res;
}

export async function GET(request: NextRequest) {
    const searchParams = request.nextUrl.searchParams;
    const query = searchParams.get("query");
    const countryCode = searchParams.get("countryCode") || "US";

    if (!query) {
        return NextResponse.json({ error: "Query parameter is required" }, { status: 400 });
    }

    const clientId = process.env.TIDAL_CLIENT_ID;
    const clientSecret = process.env.TIDAL_CLIENT_SECRET;

    if (!clientId || !clientSecret) {
        return NextResponse.json({ error: "TIDAL credentials not configured" }, { status: 500 });
    }

    try {
        // 1. Search TIDAL
        const searchUrl = `https://openapi.tidal.com/v2/searchResults/${encodeURIComponent(query)}?countryCode=${countryCode}&explicitFilter=include%2C%20exclude&include=albums,albums.artists,albums.coverArt`;

        const searchRes = await fetchTidal(searchUrl, clientId, clientSecret);

        if (!searchRes.ok) {
            const errorText = await searchRes.text();
            console.error("TIDAL Search Error:", errorText);
            return NextResponse.json({ error: "Failed to search TIDAL", details: errorText }, { status: searchRes.status });
        }

        const searchData = await searchRes.json();

        //console.log("TIDAL Search Data:", JSON.stringify(searchData, null, 2));

        // Parse JSON:API response
        const albumsMap = new Map<string, unknown>();
        const artistIds = new Set<string>();
        const artworkIds = new Set<string>();

        // Process Albums from 'included' to get IDs
        if (searchData.included) {
            searchData.included.forEach((item: Record<string, any>) => {
                if (item.type === "albums") {
                    const attr = item.attributes;

                    // Duration parsing
                    let duration = 0;
                    if (attr.duration) {
                        try {
                            const match = attr.duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
                            if (match) {
                                duration = parseInt(match[1] || "0") * 3600 + parseInt(match[2] || "0") * 60 + parseInt(match[3] || "0");
                            }
                        } catch (e) { }
                    }

                    // Collect Artist IDs
                    if (item.relationships?.artists?.data) {
                        const artistsData = Array.isArray(item.relationships.artists.data)
                            ? item.relationships.artists.data
                            : [item.relationships.artists.data];
                        artistsData.forEach((a: { id: string }) => artistIds.add(a.id));
                    }

                    // Collect CoverArt IDs
                    let coverArtId = null;
                    if (item.relationships?.coverArt?.data) {
                        const coverData = item.relationships.coverArt.data;
                        const firstCover = Array.isArray(coverData) ? coverData[0] : coverData;
                        if (firstCover && firstCover.id) {
                            artworkIds.add(firstCover.id);
                            coverArtId = firstCover.id;
                        }
                    }

                    albumsMap.set(item.id, {
                        id: item.id,
                        title: attr.title,
                        artists: [], // To be filled
                        duration: duration,
                        url: attr.externalLinks?.[0]?.href || `https://tidal.com/browse/album/${item.id}`,
                        releaseDate: attr.releaseDate,
                        mediaTags: attr.mediaTags || [],
                        imageCover: [], // To be filled
                        _artistIds: item.relationships?.artists?.data?.map((a: { id: string }) => a.id) || [],
                        _coverArtId: coverArtId
                    });
                }
            });
        }

        // Helper to chunk array
        const chunkArray = (array: string[], size: number) => {
            const chunks = [];
            for (let i = 0; i < array.length; i += size) {
                chunks.push(array.slice(i, i + size));
            }
            return chunks;
        };

        // 2. Bulk Fetch Artists
        const artistsMap = new Map<string, { name: string }>();
        if (artistIds.size > 0) {
            const allArtistIds = Array.from(artistIds);
            const chunks = chunkArray(allArtistIds, 20); // API Limit: 20

            await Promise.all(chunks.map(async (chunk) => {
                const ids = chunk.join(",");
                const artistsUrl = `https://openapi.tidal.com/v2/artists?countryCode=${countryCode}&filter[id]=${ids}`;
                try {
                    const res = await fetchTidal(artistsUrl, clientId, clientSecret);
                    if (res.ok) {
                        const data = await res.json();
                        data.data?.forEach((item: Record<string, any>) => {
                            artistsMap.set(item.id, { name: item.attributes.name });
                        });
                    } else {
                        console.error("Failed to fetch artists batch:", await res.text());
                    }
                } catch (e) {
                    console.error("Error fetching artists batch", e);
                }
            }));
        }

        // 3. Bulk Fetch Artworks
        const artworksMap = new Map<string, { url: string, width: number, height: number }[]>();
        if (artworkIds.size > 0) {
            const allArtworkIds = Array.from(artworkIds);
            const chunks = chunkArray(allArtworkIds, 20); // API Limit: 20 (assumed same as artists)

            await Promise.all(chunks.map(async (chunk) => {
                const ids = chunk.join(",");
                const artworksUrl = `https://openapi.tidal.com/v2/artworks?countryCode=${countryCode}&filter[id]=${ids}`;
                try {
                    const res = await fetchTidal(artworksUrl, clientId, clientSecret);
                    if (res.ok) {
                        const data = await res.json();
                        data.data?.forEach((item: Record<string, any>) => {
                            const images: { url: string, width: number, height: number }[] = [];

                            // Structure seen in logs: item.attributes.files is an array of { href, meta: { width, height } }
                            if (item.attributes.files && Array.isArray(item.attributes.files)) {
                                item.attributes.files.forEach((file: Record<string, any>) => {
                                    images.push({
                                        url: file.href,
                                        width: file.meta?.width || 0,
                                        height: file.meta?.height || 0
                                    });
                                });
                            }
                            // Fallback structure (imageLinks)
                            else if (item.attributes.imageLinks && Array.isArray(item.attributes.imageLinks)) {
                                item.attributes.imageLinks.forEach((link: Record<string, any>) => {
                                    images.push({ url: link.href, width: link.width || 0, height: link.height || 0 });
                                });
                            }

                            // Priority 3: Fallback - Construct URLs manually if we have an ID and parsing failed
                            if (images.length === 0) {
                                const availableSizes = [80, 160, 320, 640, 1280];
                                availableSizes.forEach(size => {
                                    images.push({
                                        url: `https://resources.tidal.com/images/${item.id.replace(/-/g, '/')}/${size}x${size}.jpg`,
                                        width: size,
                                        height: size
                                    });
                                });
                            }

                            artworksMap.set(item.id, images);
                        });
                    }
                } catch (e) { console.error("Error fetching artworks batch", e); }
            }));
        }

        // 4. Merge Data
        const albums = Array.from(albumsMap.values() as IterableIterator<Record<string, any>>).map(album => {
            // Fill Artists
            if (album._artistIds) {
                album.artists = album._artistIds.map((id: string) => artistsMap.get(id) || { name: "Unknown" });
            }
            // Fill Artwork
            if (album._coverArtId) {
                const covers = artworksMap.get(album._coverArtId);
                if (covers) {
                    album.imageCover = covers;
                } else {
                    // Fallback to direct resource URL if we have the ID but fetch failed
                    album.imageCover = [
                        { url: `https://resources.tidal.com/images/${album._coverArtId.replace(/-/g, '/')}/640x640.jpg`, width: 640, height: 640 },
                        { url: `https://resources.tidal.com/images/${album._coverArtId.replace(/-/g, '/')}/320x320.jpg`, width: 320, height: 320 }
                    ];
                }
            }

            // Cleanup internal keys
            delete album._artistIds;
            delete album._coverArtId;

            return album;
        });

        return NextResponse.json({ albums });

    } catch (error) {
        console.error("Search API Error:", error);
        return NextResponse.json({ error: "Internal server error" }, { status: 500 });
    }
}
