import axios from 'axios';
import { db } from './db.js';
import fs from 'fs';
import path from 'path';
// Generate a random local playlist cover image URL
function generateRandomPlaylistImage() {
    try {
        const coversDir = path.join(process.cwd(), 'public', 'playlist-covers');
        console.log(`[PLAYLIST COVER] Looking for images in directory: ${coversDir}`);
        // Check if directory exists
        if (!fs.existsSync(coversDir)) {
            console.log('[PLAYLIST COVER] Directory not found, creating it...');
            fs.mkdirSync(coversDir, { recursive: true });
        }
        // Get all image files from the directory (Spotify only accepts JPEG for cover uploads)
        const files = fs.readdirSync(coversDir).filter(file => {
            const ext = path.extname(file).toLowerCase();
            return ['.jpg', '.jpeg'].includes(ext); // Only JPEG files for Spotify upload
        });
        console.log(`[PLAYLIST COVER] Found ${files.length} image files:`, files);
        // Log file sizes for debugging
        files.forEach(file => {
            const filePath = path.join(coversDir, file);
            const stats = fs.statSync(filePath);
            const sizeKB = (stats.size / 1024).toFixed(2);
            console.log(`[PLAYLIST COVER] File: ${file}, Size: ${sizeKB} KB`);
        });
        if (files.length === 0) {
            console.log('[PLAYLIST COVER] No playlist cover images found in public/playlist-covers/');
            return '/api/placeholder-playlist-cover';
        }
        // Select a random image
        const randomFile = files[Math.floor(Math.random() * files.length)];
        const imageUrl = `/api/playlist-covers/${encodeURIComponent(randomFile)}`;
        console.log(`[PLAYLIST COVER] Selected random playlist cover: ${imageUrl} (from file: ${randomFile})`);
        return imageUrl;
    }
    catch (error) {
        console.error('[PLAYLIST COVER] Error generating random playlist image:', error);
        return '/api/placeholder-playlist-cover';
    }
}
export function getUserTokens(uid) {
    const row = db.db
        .prepare('SELECT access_token, refresh_token, token_expires_at FROM users WHERE id = ?')
        .get(uid);
    if (!row)
        return null;
    return {
        access_token: row.access_token,
        refresh_token: row.refresh_token,
        token_expires_at: row.token_expires_at,
    };
}
export async function ensureValidAccessToken(uid) {
    const tokens = getUserTokens(uid);
    if (!tokens?.access_token) {
        throw new Error('User not authenticated');
    }
    const now = Date.now();
    if (tokens.token_expires_at && tokens.token_expires_at > now + 30000) {
        return tokens.access_token;
    }
    if (!tokens.refresh_token) {
        return tokens.access_token;
    }
    // Refresh token via Spotify
    const clientId = process.env.SPOTIFY_CLIENT_ID || '';
    const clientSecret = process.env.SPOTIFY_CLIENT_SECRET || '';
    const resp = await axios.post('https://accounts.spotify.com/api/token', new URLSearchParams({ grant_type: 'refresh_token', refresh_token: tokens.refresh_token }), {
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,
        },
    });
    const { access_token, expires_in } = resp.data;
    const token_expires_at = Date.now() + expires_in * 1000 - 60000;
    db.db
        .prepare('UPDATE users SET access_token = ?, token_expires_at = ?, updated_at=? WHERE id = ?')
        .run(access_token, token_expires_at, Date.now(), uid);
    return access_token;
}
export function vibeToTargets(vibe) {
    switch (vibe) {
        case 'energetic':
            return { target_energy: 0.85, target_danceability: 0.7, target_tempo: 130 };
        case 'chill':
            return { target_energy: 0.3, target_danceability: 0.4, target_tempo: 90 };
        case 'happy':
            return { target_valence: 0.85, target_danceability: 0.6 };
        case 'sad':
            return { target_valence: 0.2 };
        case 'party':
            return { target_danceability: 0.85, target_energy: 0.8, target_tempo: 125 };
        case 'focus':
            return { target_energy: 0.4, target_instrumentalness: 0.5 };
        default:
            return {};
    }
}
export async function fetchRecommendations(accessToken, seeds, targets, limit = 25) {
    // Legacy kept for compatibility; prefer fetchSimilarTrackUris below
    return fetchSimilarTrackUris(accessToken, seeds.seed_tracks, seeds.seed_artists, seeds.seed_genres, targets, limit);
}
export async function createSpotifyPlaylist(accessToken, userId, name, description) {
    console.log(`[PLAYLIST CREATION] Creating Spotify playlist: "${name}" for user: ${userId}`);
    const resp = await axios.post(`https://api.spotify.com/v1/users/${encodeURIComponent(userId)}/playlists`, { name, description, public: false }, { headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' } });
    const pl = resp.data;
    console.log(`[PLAYLIST CREATION] Spotify playlist created successfully with ID: ${pl.id}`);
    // Generate a random local playlist cover image
    const imageUrl = generateRandomPlaylistImage();
    // Extract the filename from the imageUrl to use the SAME image for Spotify upload
    const selectedFilename = decodeURIComponent(imageUrl.replace('/api/playlist-covers/', ''));
    console.log(`[PLAYLIST COVER] Selected image for both website and Spotify: ${selectedFilename}`);
    // Get the actual file path for uploading to Spotify (only JPEG files)
    const coversDir = path.join(process.cwd(), 'public', 'playlist-covers');
    const files = fs.readdirSync(coversDir).filter(file => {
        const ext = path.extname(file).toLowerCase();
        return ['.jpg', '.jpeg'].includes(ext); // Only JPEG files for Spotify upload
    });
    if (files.length > 0) {
        // Find the specific file that was selected for the website
        const selectedFile = files.find(file => file === selectedFilename);
        if (selectedFile) {
            const imagePath = path.join(coversDir, selectedFile);
            const stats = fs.statSync(imagePath);
            const fileSizeKB = (stats.size / 1024).toFixed(2);
            console.log(`[PLAYLIST COVER] Using SAME image for Spotify upload: ${selectedFile} (${fileSizeKB} KB)`);
            // Upload the SAME cover image to Spotify
            const uploadSuccess = await uploadPlaylistCover(accessToken, pl.id, imagePath);
            if (uploadSuccess) {
                console.log(`[PLAYLIST COVER] ✅ Successfully uploaded SAME cover image to Spotify playlist ${pl.id}`);
            }
            else {
                console.log(`[PLAYLIST COVER] ❌ Failed to upload ${selectedFile} to Spotify, but website will still show it`);
            }
        }
        else {
            console.log(`[PLAYLIST COVER] ⚠️  Could not find selected file ${selectedFilename} for Spotify upload`);
            console.log(`[PLAYLIST COVER] ✅  WEBSITE COVER: Random cover image will still be displayed on your website.`);
        }
    }
    return { id: pl.id, url: pl.external_urls?.spotify, imageUrl };
}
export async function addTracks(accessToken, playlistId, trackUris) {
    if (!trackUris.length)
        return;
    await axios.post(`https://api.spotify.com/v1/playlists/${encodeURIComponent(playlistId)}/tracks`, { uris: trackUris }, { headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' } });
}
// Upload a playlist cover image to Spotify
export async function uploadPlaylistCover(accessToken, playlistId, imagePath) {
    try {
        console.log(`[PLAYLIST COVER] Attempting to upload cover image for playlist ${playlistId} from path: ${imagePath}`);
        // Check if file exists
        if (!fs.existsSync(imagePath)) {
            console.error(`[PLAYLIST COVER] Image file not found: ${imagePath}`);
            return false;
        }
        // Read the image file
        let imageBuffer = fs.readFileSync(imagePath);
        let fileSize = imageBuffer.length;
        console.log(`[PLAYLIST COVER] Original image file size: ${fileSize} bytes (${(fileSize / 1024).toFixed(2)} KB)`);
        // Check if we need to compress the image
        // We need to account for base64 encoding overhead (33% increase)
        // So we want the original file to be under ~190 KB to stay under 256 KB when base64 encoded
        const maxOriginalSize = 190 * 1024; // 190 KB
        if (fileSize > maxOriginalSize) {
            console.log(`[PLAYLIST COVER] Image too large (${(fileSize / 1024).toFixed(2)} KB), attempting to compress...`);
            // For now, let's skip files that are too large and try a different one
            console.error(`[PLAYLIST COVER] Image file too large for Spotify upload: ${fileSize} bytes (max ~190 KB to account for base64 overhead)`);
            return false;
        }
        // Convert to base64
        let base64Image = imageBuffer.toString('base64');
        const base64Size = base64Image.length;
        console.log(`[PLAYLIST COVER] Base64 length: ${base64Size} characters (${(base64Size / 1024).toFixed(2)} KB)`);
        // Ensure we don't have any data URL prefix
        if (base64Image.startsWith('data:')) {
            base64Image = base64Image.split(',')[1] || base64Image;
            console.log(`[PLAYLIST COVER] Removed data URL prefix from base64`);
        }
        // Final check - ensure base64 size is reasonable
        if (base64Size > 300 * 1024) { // 300 KB base64 limit
            console.error(`[PLAYLIST COVER] Base64 encoded image too large: ${base64Size} characters`);
            return false;
        }
        // Determine content type based on file extension
        const ext = path.extname(imagePath).toLowerCase();
        let contentType = 'image/jpeg'; // default
        switch (ext) {
            case '.png':
                contentType = 'image/png';
                break;
            case '.gif':
                contentType = 'image/gif';
                break;
            case '.webp':
                contentType = 'image/webp';
                break;
            case '.svg':
                contentType = 'image/svg+xml';
                break;
            case '.jpg':
            case '.jpeg':
                contentType = 'image/jpeg';
                break;
        }
        console.log(`[PLAYLIST COVER] Using content type: ${contentType}`);
        // Check token scopes first
        try {
            console.log(`[PLAYLIST COVER] Checking token scopes...`);
            const meResponse = await axios.get('https://api.spotify.com/v1/me', {
                headers: { 'Authorization': `Bearer ${accessToken}` }
            });
            console.log(`[PLAYLIST COVER] Token is valid for user: ${meResponse.data.display_name}`);
            // Test if we can access the playlist
            const playlistResponse = await axios.get(`https://api.spotify.com/v1/playlists/${playlistId}`, {
                headers: { 'Authorization': `Bearer ${accessToken}` }
            });
            console.log(`[PLAYLIST COVER] Can access playlist: ${playlistResponse.data.name}`);
        }
        catch (error) {
            console.error(`[PLAYLIST COVER] Token validation failed:`, error.response?.data || error.message);
        }
        // Upload to Spotify
        console.log(`[PLAYLIST COVER] Making request to Spotify API: https://api.spotify.com/v1/playlists/${playlistId}/images`);
        console.log(`[PLAYLIST COVER] Access token (first 20 chars): ${accessToken.substring(0, 20)}...`);
        // Try different approaches for image upload
        let response;
        try {
            // Method 1: Standard PUT request (requires ugc-image-upload scope)
            response = await axios.put(`https://api.spotify.com/v1/playlists/${encodeURIComponent(playlistId)}/images`, base64Image, {
                headers: {
                    'Authorization': `Bearer ${accessToken}`,
                    'Content-Type': contentType
                }
            });
        }
        catch (error) {
            if (error.response?.status === 401) {
                console.log(`[PLAYLIST COVER] Method 1 failed with 401, trying alternative approach...`);
                // Method 2: Try with different content type
                try {
                    response = await axios.put(`https://api.spotify.com/v1/playlists/${encodeURIComponent(playlistId)}/images`, base64Image, {
                        headers: {
                            'Authorization': `Bearer ${accessToken}`,
                            'Content-Type': 'image/jpeg' // Force JPEG content type
                        }
                    });
                }
                catch (error2) {
                    console.log(`[PLAYLIST COVER] Method 2 also failed: ${error2.response?.status}`);
                    throw error; // Re-throw original error
                }
            }
            else {
                throw error; // Re-throw if not 401
            }
        }
        console.log(`[PLAYLIST COVER] Successfully uploaded cover image for playlist ${playlistId}, response status: ${response.status}`);
        console.log(`[PLAYLIST COVER] Response headers:`, response.headers);
        return true;
    }
    catch (error) {
        console.error(`[PLAYLIST COVER] Failed to upload cover image for playlist ${playlistId}:`);
        console.error(`[PLAYLIST COVER] Error status:`, error.response?.status);
        console.error(`[PLAYLIST COVER] Error data:`, error.response?.data);
        console.error(`[PLAYLIST COVER] Error message:`, error.message);
        return false;
    }
}
// ----- New: Similar tracks without recommendations endpoint -----
async function getRelatedArtists(accessToken, artistId) {
    const resp = await axios.get(`https://api.spotify.com/v1/artists/${encodeURIComponent(artistId)}/related-artists`, {
        headers: { Authorization: `Bearer ${accessToken}` },
    });
    const artists = (resp.data?.artists || []);
    return artists.map(a => a.id).filter(Boolean);
}
async function getArtistTopTracks(accessToken, artistId) {
    const resp = await axios.get(`https://api.spotify.com/v1/artists/${encodeURIComponent(artistId)}/top-tracks?market=from_token`, {
        headers: { Authorization: `Bearer ${accessToken}` },
    });
    const tracks = (resp.data?.tracks || []);
    return tracks.map(t => t.uri || (t.id ? `spotify:track:${t.id}` : null)).filter((u) => !!u);
}
async function searchTracksByGenre(accessToken, genre, limit) {
    const resp = await axios.get('https://api.spotify.com/v1/search', {
        headers: { Authorization: `Bearer ${accessToken}` },
        params: { q: `genre:"${genre}"`, type: 'track', limit: Math.min(limit, 50) },
    });
    const tracks = (resp.data?.tracks?.items || []);
    return tracks.map(t => t.uri || (t.id ? `spotify:track:${t.id}` : null)).filter((u) => !!u);
}
async function filterByTargets(accessToken, trackUris, targets) {
    if (!trackUris.length || Object.keys(targets).length === 0)
        return trackUris;
    const ids = trackUris.map(u => u.split(':').pop()).filter(Boolean);
    const resp = await axios.get('https://api.spotify.com/v1/audio-features', {
        headers: { Authorization: `Bearer ${accessToken}` },
        params: { ids: ids.slice(0, 100).join(',') },
    });
    const features = (resp.data?.audio_features || []);
    const pass = new Set(features
        .filter(f => !!f && (!('target_energy' in targets) || Math.abs(targets.target_energy - f.energy) <= 0.4))
        .filter(f => !('target_danceability' in targets) || Math.abs(targets.target_danceability - f.danceability) <= 0.4)
        .filter(f => !('target_valence' in targets) || Math.abs(targets.target_valence - f.valence) <= 0.5)
        .filter(f => !('target_tempo' in targets) || Math.abs(targets.target_tempo - f.tempo) <= 30)
        .filter(f => !('target_instrumentalness' in targets) || Math.abs(targets.target_instrumentalness - f.instrumentalness) <= 0.5)
        .map(f => f.id));
    return trackUris.filter(u => pass.has(u.split(':').pop()));
}
export async function fetchSimilarTrackUris(accessToken, seedTrackUris, seedArtistIds, seedGenres, targets = {}, limit = 25) {
    const candidateUris = new Set();
    // From seed artists and from artists of seed tracks
    const trackIds = (seedTrackUris || []).map(u => u?.split(':').pop()).filter(Boolean);
    const seedArtistSet = new Set(seedArtistIds || []);
    // If we got tracks, fetch their artists to expand
    if (trackIds.length) {
        const tracksResp = await axios.get('https://api.spotify.com/v1/tracks', {
            headers: { Authorization: `Bearer ${accessToken}` },
            params: { ids: trackIds.slice(0, 50).join(',') },
        });
        const tracks = (tracksResp.data?.tracks || []);
        tracks.forEach(t => (t.artists || []).forEach(a => a?.id && seedArtistSet.add(a.id)));
    }
    // Expand via related artists and take top tracks
    const seedArtists = Array.from(seedArtistSet).slice(0, 10);
    for (const artistId of seedArtists) {
        try {
            const related = (await getRelatedArtists(accessToken, artistId)).slice(0, 10);
            const pool = [artistId, ...related];
            for (const a of pool) {
                const tops = await getArtistTopTracks(accessToken, a);
                tops.forEach(u => candidateUris.add(u));
                if (candidateUris.size >= limit * 4)
                    break;
            }
            if (candidateUris.size >= limit * 4)
                break;
        }
        catch { }
    }
    // Genre-based search fallback
    for (const g of (seedGenres || []).slice(0, 3)) {
        try {
            const found = await searchTracksByGenre(accessToken, g, 50);
            found.forEach(u => candidateUris.add(u));
            if (candidateUris.size >= limit * 4)
                break;
        }
        catch { }
    }
    let uris = Array.from(candidateUris);
    uris = await filterByTargets(accessToken, uris, targets);
    // Shuffle and take limit
    uris.sort(() => Math.random() - 0.5);
    return uris.slice(0, limit);
}
