import { Router } from 'express';
import { db } from '../lib/db.js';
import axios from 'axios';
import { requireAuth } from '../middleware/auth.js';
import { syncUserData } from '../lib/sync.js';
import { ensureValidAccessToken } from '../lib/spotify.js';
// Helper functions for unique categories
function calculateLongestSession(tracks) {
    if (tracks.length === 0)
        return { duration: 0, startTime: 0, endTime: 0 };
    const sortedTracks = tracks.sort((a, b) => a.played_at - b.played_at);
    let longestSession = { duration: 0, startTime: 0, endTime: 0 };
    let currentSession = { startTime: sortedTracks[0]?.played_at || 0, endTime: sortedTracks[0]?.played_at || 0 };
    for (let i = 1; i < sortedTracks.length; i++) {
        const timeDiff = sortedTracks[i].played_at - sortedTracks[i - 1].played_at;
        if (timeDiff <= 30 * 60 * 1000) { // 30 minutes gap
            currentSession.endTime = sortedTracks[i].played_at;
        }
        else {
            const sessionDuration = currentSession.endTime - currentSession.startTime;
            if (sessionDuration > longestSession.duration) {
                longestSession = { ...currentSession, duration: sessionDuration };
            }
            currentSession = { startTime: sortedTracks[i].played_at, endTime: sortedTracks[i].played_at };
        }
    }
    // Check the final session
    const finalSessionDuration = currentSession.endTime - currentSession.startTime;
    if (finalSessionDuration > longestSession.duration) {
        longestSession = { ...currentSession, duration: finalSessionDuration };
    }
    // If no session is longer than 0, use the first track as a 1-minute session
    if (longestSession.duration === 0 && tracks.length > 0) {
        const firstTrack = sortedTracks[0];
        longestSession = {
            startTime: firstTrack.played_at,
            endTime: firstTrack.played_at + 60000, // 1 minute
            duration: 60000
        };
    }
    return longestSession;
}
function calculateMostDiverseDay(tracks) {
    const dayGroups = tracks.reduce((acc, track) => {
        const date = new Date(track.played_at).toDateString();
        if (!acc[date])
            acc[date] = new Set();
        track.artists?.forEach((artist) => acc[date].add(artist.id));
        return acc;
    }, {});
    let mostDiverse = { date: '', artistCount: 0 };
    Object.entries(dayGroups).forEach(([date, artists]) => {
        if (artists.size > mostDiverse.artistCount) {
            mostDiverse = { date, artistCount: artists.size };
        }
    });
    return mostDiverse;
}
function getMostActiveMonth(tracks) {
    const monthGroups = tracks.reduce((acc, track) => {
        const date = new Date(track.played_at);
        const monthKey = `${date.getFullYear()}-${date.getMonth() + 1}`;
        acc[monthKey] = (acc[monthKey] || 0) + 1;
        return acc;
    }, {});
    let mostActive = { month: '', count: 0 };
    Object.entries(monthGroups).forEach(([month, count]) => {
        if (count > mostActive.count) {
            mostActive = { month, count: count };
        }
    });
    return mostActive;
}
function calculateListeningVelocity(tracks, timeRange) {
    if (tracks.length === 0)
        return 0;
    let daysInRange = 1;
    if (timeRange === 'year')
        daysInRange = 365;
    else if (timeRange === 'month')
        daysInRange = 30;
    else if (timeRange === 'week')
        daysInRange = 7;
    else {
        const firstTrack = Math.min(...tracks.map(t => t.played_at));
        const lastTrack = Math.max(...tracks.map(t => t.played_at));
        daysInRange = Math.max(1, (lastTrack - firstTrack) / (1000 * 60 * 60 * 24));
    }
    return tracks.length / daysInRange;
}
export const usersRouter = Router();
// Fetch and store user data (recently played, top tracks/artists)
usersRouter.post('/:uid/sync', requireAuth, async (req, res) => {
    try {
        const { uid } = req.params;
        if (!req.user || req.user.uid !== uid)
            return res.status(403).json({ error: 'Forbidden' });
        await syncUserData(uid);
        res.json({ ok: true });
    }
    catch (err) {
        console.error(err.response?.data || err.message);
        res.status(500).json({ error: 'Sync failed' });
    }
});
usersRouter.get('/:uid', (req, res) => {
    const { uid } = req.params;
    const user = db.db.prepare('SELECT id, display_name, email, avatar_url, country, product, created_at, updated_at, last_synced_at FROM users WHERE id = ?').get(uid);
    if (!user)
        return res.status(404).json({ error: 'Not found' });
    res.json(user);
});
// Simple search by display_name (substring, case-insensitive)
usersRouter.get('/search/q', (req, res) => {
    const q = String(req.query.q || '').trim();
    if (!q)
        return res.json([]);
    const rows = db.db
        .prepare('SELECT id, display_name, avatar_url FROM users WHERE lower(display_name) LIKE ? LIMIT 20')
        .all(`%${q.toLowerCase()}%`);
    res.json(rows);
});
usersRouter.get('/:uid/recently-played', (req, res) => {
    const { uid } = req.params;
    const rows = db.db.prepare('SELECT id, user_id, played_at, track_json FROM recently_played WHERE user_id = ? ORDER BY played_at DESC LIMIT 50').all(uid);
    res.json(rows.map((r) => ({ id: r.id, user_id: r.user_id, played_at: r.played_at, track: JSON.parse(r.track_json) })));
});
usersRouter.get('/:uid/top-tracks', (req, res) => {
    const { uid } = req.params;
    const range = req.query.time_range || 'short_term';
    const rows = db.db.prepare('SELECT rank, track_json FROM top_tracks WHERE user_id = ? AND time_range = ? ORDER BY rank ASC').all(uid, range);
    res.json(rows.map((r) => ({ rank: r.rank, track: JSON.parse(r.track_json) })));
});
usersRouter.get('/:uid/top-artists', (req, res) => {
    const { uid } = req.params;
    const range = req.query.time_range || 'short_term';
    const rows = db.db.prepare('SELECT rank, artist_json FROM top_artists WHERE user_id = ? AND time_range = ? ORDER BY rank ASC').all(uid, range);
    res.json(rows.map((r) => ({ rank: r.rank, artist: JSON.parse(r.artist_json) })));
});
// status: last sync time and next scheduled time (based on 10 min interval)
usersRouter.get('/:uid/status', (req, res) => {
    const { uid } = req.params;
    const row = db.db.prepare('SELECT last_synced_at FROM users WHERE id = ?').get(uid);
    const last = row?.last_synced_at || null;
    // Match backend full sync interval (2 minutes)
    const next = last ? last + 2 * 60 * 1000 : null;
    res.json({ lastSyncedAt: last, nextSyncAt: next });
});
// Now playing (with token refresh)
usersRouter.get('/:uid/now-playing', requireAuth, async (req, res) => {
    try {
        const { uid } = req.params;
        if (!req.user)
            return res.status(403).json({ error: 'Forbidden' });
        if (req.user.uid !== uid) {
            // allow if users are partnered
            const paired = db.db.prepare('SELECT 1 FROM friendships WHERE (user_a_id=? AND user_b_id=?) OR (user_a_id=? AND user_b_id=?)').get(req.user.uid, uid, uid, req.user.uid);
            if (!paired)
                return res.status(403).json({ error: 'Forbidden' });
        }
        // Try DB-captured snapshot first (last 60s)
        const recent = db.db.prepare('SELECT played_at, track_json FROM recently_played WHERE user_id=? ORDER BY played_at DESC LIMIT 1').get(uid);
        if (recent?.played_at && Date.now() - recent.played_at < 60000 && recent?.track_json) {
            try {
                const item = JSON.parse(recent.track_json);
                return res.json({ is_playing: true, item, source: 'db' });
            }
            catch { }
        }
        const accessToken = await ensureValidAccessToken(uid);
        const resp = await axios.get('https://api.spotify.com/v1/me/player/currently-playing?additional_types=track', {
            headers: { Authorization: `Bearer ${accessToken}` },
            validateStatus: () => true,
        });
        if (resp.status === 204)
            return res.json({ is_playing: false });
        if (resp.status >= 400) {
            // Return a benign payload rather than 500 to avoid breaking UI when Spotify returns errors
            return res.json({ is_playing: false });
        }
        return res.status(200).json(resp.data);
    }
    catch (e) {
        res.status(200).json({ is_playing: false });
    }
});
// Audio features bulk
usersRouter.get('/:uid/audio-features', requireAuth, async (req, res) => {
    try {
        const { uid } = req.params;
        const ids = String(req.query.ids || '').split(',').map(s => s.trim()).filter(Boolean);
        if (!req.user)
            return res.status(403).json({ error: 'Forbidden' });
        if (req.user.uid !== uid) {
            const paired = db.db.prepare('SELECT 1 FROM friendships WHERE (user_a_id=? AND user_b_id=?) OR (user_a_id=? AND user_b_id=?)').get(req.user.uid, uid, uid, req.user.uid);
            if (!paired)
                return res.status(403).json({ error: 'Forbidden' });
        }
        if (!ids.length)
            return res.json({ audio_features: [] });
        const accessToken = await ensureValidAccessToken(uid);
        // Some tracks may be local or missing features; handle gracefully
        const resp = await axios.get('https://api.spotify.com/v1/audio-features', {
            headers: { Authorization: `Bearer ${accessToken}` },
            params: { ids: ids.slice(0, 100).join(',') },
        });
        res.json({ audio_features: Array.isArray(resp.data?.audio_features) ? resp.data.audio_features.filter(Boolean) : [] });
    }
    catch (e) {
        res.status(500).json({ error: 'Failed to fetch audio features', detail: e?.response?.data || e?.message });
    }
});
// Wrapped feature - generate personalized music statistics
usersRouter.get('/:uid/wrapped', requireAuth, async (req, res) => {
    try {
        const { uid } = req.params;
        if (!req.user)
            return res.status(403).json({ error: 'Forbidden' });
        if (req.user.uid !== uid) {
            const paired = db.db.prepare('SELECT 1 FROM friendships WHERE (user_a_id=? AND user_b_id=?) OR (user_a_id=? AND user_b_id=?)').get(req.user.uid, uid, uid, req.user.uid);
            if (!paired)
                return res.status(403).json({ error: 'Forbidden' });
        }
        // Get time range from query (default to all time)
        const timeRange = req.query.range || 'all';
        let timeFilter = '';
        let timeParams = [uid];
        if (timeRange === 'year') {
            const oneYearAgo = Date.now() - (365 * 24 * 60 * 60 * 1000);
            timeFilter = 'AND played_at >= ?';
            timeParams.push(oneYearAgo);
        }
        else if (timeRange === 'month') {
            const oneMonthAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
            timeFilter = 'AND played_at >= ?';
            timeParams.push(oneMonthAgo);
        }
        else if (timeRange === 'week') {
            const oneWeekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
            timeFilter = 'AND played_at >= ?';
            timeParams.push(oneWeekAgo);
        }
        // Get all played tracks for analysis
        const playedTracks = db.db.prepare(`
      SELECT played_at, track_json 
      FROM recently_played 
      WHERE user_id = ? ${timeFilter}
      ORDER BY played_at DESC
    `).all(...timeParams);
        if (playedTracks.length === 0) {
            return res.json({
                totalTracks: 0,
                totalListeningTime: 0,
                topSongs: [],
                topArtists: [],
                topGenres: [],
                listeningPatterns: {
                    mostActiveHour: 0,
                    mostActiveDay: 'Monday',
                    listeningStreak: 0
                },
                timeRange,
                generatedAt: Date.now()
            });
        }
        // Parse tracks and calculate statistics
        const tracks = playedTracks.map(row => {
            const track = JSON.parse(row.track_json);
            return {
                ...track,
                played_at: row.played_at,
                duration_ms: track.duration_ms || 0
            };
        });
        // Calculate total listening time
        const totalListeningTime = tracks.reduce((sum, track) => sum + (track.duration_ms || 0), 0);
        // Top songs (by play count)
        const songCounts = new Map();
        tracks.forEach(track => {
            const key = track.id;
            if (songCounts.has(key)) {
                const existing = songCounts.get(key);
                existing.count++;
                existing.totalDuration += track.duration_ms || 0;
            }
            else {
                songCounts.set(key, { track, count: 1, totalDuration: track.duration_ms || 0 });
            }
        });
        const topSongs = Array.from(songCounts.values())
            .sort((a, b) => b.count - a.count)
            .slice(0, 10)
            .map(item => ({
            ...item.track,
            playCount: item.count,
            totalDuration: item.totalDuration
        }));
        // Top artists (by play count)
        const artistCounts = new Map();
        tracks.forEach(track => {
            track.artists?.forEach((artist) => {
                const key = artist.id;
                if (artistCounts.has(key)) {
                    const existing = artistCounts.get(key);
                    existing.count++;
                    existing.totalDuration += track.duration_ms || 0;
                }
                else {
                    artistCounts.set(key, { artist, count: 1, totalDuration: track.duration_ms || 0 });
                }
            });
        });
        const topArtists = Array.from(artistCounts.values())
            .sort((a, b) => b.count - a.count)
            .slice(0, 10)
            .map(item => ({
            ...item.artist,
            playCount: item.count,
            totalDuration: item.totalDuration
        }));
        // Top genres (extract from track data if available)
        const genreCounts = new Map();
        tracks.forEach(track => {
            // Try to extract genres from track data
            if (track.album?.genres) {
                track.album.genres.forEach((genre) => {
                    genreCounts.set(genre, (genreCounts.get(genre) || 0) + 1);
                });
            }
        });
        const topGenres = Array.from(genreCounts.entries())
            .sort((a, b) => b[1] - a[1])
            .slice(0, 10)
            .map(([genre, count]) => ({ genre, count }));
        // Listening patterns
        const hours = new Array(24).fill(0);
        const days = new Array(7).fill(0);
        const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
        tracks.forEach(track => {
            const date = new Date(track.played_at);
            hours[date.getHours()]++;
            days[date.getDay()]++;
        });
        const mostActiveHour = hours.indexOf(Math.max(...hours));
        const mostActiveDay = dayNames[days.indexOf(Math.max(...days))];
        // Calculate listening streak (consecutive days with at least one play)
        const playDates = new Set(tracks.map(track => new Date(track.played_at).toDateString()));
        const sortedDates = Array.from(playDates).sort().reverse();
        let streak = 0;
        let currentDate = new Date();
        let lastPlayDate = null;
        for (let i = 0; i < sortedDates.length; i++) {
            const playDate = new Date(sortedDates[i]);
            const daysDiff = lastPlayDate ?
                Math.floor((lastPlayDate.getTime() - playDate.getTime()) / (1000 * 60 * 60 * 24)) :
                Math.floor((currentDate.getTime() - playDate.getTime()) / (1000 * 60 * 60 * 24));
            if (daysDiff === 1 || (i === 0 && daysDiff <= 1)) {
                streak++;
                lastPlayDate = playDate;
            }
            else {
                break;
            }
        }
        // Get partner data if available
        let partnerData = null;
        try {
            const partner = db.db.prepare('SELECT user_b_id FROM friendships WHERE user_a_id = ? UNION SELECT user_a_id FROM friendships WHERE user_b_id = ?').get(uid, uid);
            if (partner?.user_b_id) {
                const partnerTracks = db.db.prepare(`
          SELECT played_at, track_json 
          FROM recently_played 
          WHERE user_id = ? ${timeFilter}
          ORDER BY played_at DESC
        `).all(...timeParams.slice(0, -1), partner.user_b_id, ...timeParams.slice(-1));
                if (partnerTracks.length > 0) {
                    const partnerTracksParsed = partnerTracks.map(row => {
                        const track = JSON.parse(row.track_json);
                        return { ...track, played_at: row.played_at, duration_ms: track.duration_ms || 0 };
                    });
                    partnerData = {
                        totalTracks: partnerTracksParsed.length,
                        totalListeningTime: partnerTracksParsed.reduce((sum, track) => sum + (track.duration_ms || 0), 0),
                        topSongs: partnerTracksParsed.slice(0, 5),
                        topArtists: partnerTracksParsed.slice(0, 5).map(track => track.artists?.[0]).filter(Boolean)
                    };
                }
            }
        }
        catch (e) {
            // Partner data not available
        }
        // Calculate additional unique categories
        const uniqueCategories = {
            // Most played song this week
            songOfTheWeek: tracks.filter(track => {
                const weekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
                return track.played_at >= weekAgo;
            }).reduce((acc, track) => {
                acc[track.id] = (acc[track.id] || 0) + 1;
                return acc;
            }, {}),
            // Longest listening session (consecutive plays)
            longestSession: calculateLongestSession(tracks),
            // Most diverse day (most unique artists in one day)
            mostDiverseDay: calculateMostDiverseDay(tracks),
            // Late night listener (plays after 11 PM)
            lateNightListener: tracks.filter(track => new Date(track.played_at).getHours() >= 23).length,
            // Early bird (plays before 6 AM)
            earlyBird: tracks.filter(track => new Date(track.played_at).getHours() < 6).length,
            // Weekend warrior (plays on weekends)
            weekendWarrior: tracks.filter(track => {
                const day = new Date(track.played_at).getDay();
                return day === 0 || day === 6;
            }).length,
            // Discovery rate (new artists discovered)
            discoveryRate: new Set(tracks.map(track => track.artists?.[0]?.id).filter(Boolean)).size,
            // Average song length
            averageSongLength: tracks.length > 0 ? tracks.reduce((sum, track) => sum + (track.duration_ms || 0), 0) / tracks.length : 0,
            // Most active month
            mostActiveMonth: getMostActiveMonth(tracks),
            // Listening velocity (songs per day)
            listeningVelocity: calculateListeningVelocity(tracks, timeRange)
        };
        // Get data collection start date
        const firstPlay = db.db.prepare('SELECT MIN(played_at) as first_play FROM recently_played WHERE user_id = ?').get(uid);
        const dataCollectionStart = firstPlay?.first_play || Date.now();
        res.json({
            totalTracks: tracks.length,
            totalListeningTime,
            topSongs,
            topArtists,
            topGenres,
            listeningPatterns: {
                mostActiveHour,
                mostActiveDay,
                listeningStreak: streak
            },
            uniqueCategories,
            partnerData,
            dataCollectionStart,
            timeRange,
            generatedAt: Date.now()
        });
    }
    catch (e) {
        console.error('Wrapped generation error:', e);
        res.status(500).json({ error: 'Failed to generate wrapped data', detail: e?.message });
    }
});
// Get artist details with profile picture
usersRouter.get('/:uid/artist/:artistId', requireAuth, async (req, res) => {
    try {
        const { uid, artistId } = req.params;
        if (!req.user)
            return res.status(403).json({ error: 'Forbidden' });
        if (req.user.uid !== uid) {
            const paired = db.db.prepare('SELECT 1 FROM friendships WHERE (user_a_id=? AND user_b_id=?) OR (user_a_id=? AND user_b_id=?)').get(req.user.uid, uid, uid, req.user.uid);
            if (!paired)
                return res.status(403).json({ error: 'Forbidden' });
        }
        const accessToken = await ensureValidAccessToken(uid);
        const response = await axios.get(`https://api.spotify.com/v1/artists/${artistId}`, {
            headers: { Authorization: `Bearer ${accessToken}` }
        });
        res.json(response.data);
    }
    catch (e) {
        console.error('Artist fetch error:', e);
        res.status(500).json({ error: 'Failed to fetch artist data', detail: e?.message });
    }
});
