using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using NodaTime;
using SpotifyAPI.Web;

namespace DysonNetwork.Pass.Account.Presences;

public class SpotifyPresenceService(
    AppDatabase db,
    Auth.OpenId.SpotifyOidcService spotifyService,
    AccountEventService accountEventService,
    ILogger<SpotifyPresenceService> logger
) : IPresenceService
{
    /// <inheritdoc />
    public string ServiceId => "spotify";

    /// <inheritdoc />
    public async Task UpdatePresencesAsync(IEnumerable<Guid> userIds)
    {
        var userIdList = userIds.ToList();
        var userConnections = await db.AccountConnections
            .Where(c => userIdList.Contains(c.AccountId) && c.Provider == "spotify" && c.AccessToken != null && c.RefreshToken != null)
            .Include(c => c.Account)
            .ToListAsync();

        foreach (var connection in userConnections)
            await UpdateSpotifyPresenceAsync(connection.Account);
    }

    /// <summary>
    /// Updates the Spotify presence activity for a specific user
    /// </summary>
    private async Task UpdateSpotifyPresenceAsync(SnAccount account)
    {
        var connection = await db.AccountConnections
            .FirstOrDefaultAsync(c => c.AccountId == account.Id && c.Provider == "spotify");

        if (connection?.RefreshToken == null)
        {
            // No Spotify connection, remove any existing Spotify presence
            await RemoveSpotifyPresenceAsync(account.Id);
            return;
        }

        try
        {
            // Ensure we have a valid access token
            var validToken = await spotifyService.GetValidAccessTokenAsync(connection.RefreshToken, connection.AccessToken);
            if (string.IsNullOrEmpty(validToken))
            {
                // Couldn't get a valid token, remove presence
                await RemoveSpotifyPresenceAsync(account.Id);
                return;
            }

            // Create Spotify client with the valid token
            var spotify = new SpotifyClient(validToken);

            // Get currently playing track
            var currentlyPlaying = await spotify.Player.GetCurrentlyPlaying(new PlayerCurrentlyPlayingRequest());
            if (currentlyPlaying?.Item == null || !currentlyPlaying.IsPlaying)
            {
                // Nothing playing or paused, remove the presence
                await RemoveSpotifyPresenceAsync(account.Id);
                return;
            }

            var presenceActivity = ParseCurrentlyPlayingToPresenceActivity(account.Id, currentlyPlaying);

            // Try to update existing activity first
            var updatedActivity = await accountEventService.UpdateActivityByManualId(
                "spotify",
                account.Id,
                UpdateActivityWithPresenceData,
                5 
            );

            // If update failed (no existing activity), create a new one
            if (updatedActivity == null)
                await accountEventService.SetActivity(presenceActivity, 5);

            // Local function to avoid capturing external variables in lambda
            void UpdateActivityWithPresenceData(SnPresenceActivity activity)
            {
                activity.Type = PresenceType.Music;
                activity.Title = presenceActivity.Title;
                activity.Subtitle = presenceActivity.Subtitle;
                activity.Caption = presenceActivity.Caption;
                activity.LargeImage = presenceActivity.LargeImage;
                activity.SmallImage = presenceActivity.SmallImage;
                activity.TitleUrl = presenceActivity.TitleUrl;
                activity.SubtitleUrl = presenceActivity.SubtitleUrl;
                activity.Meta = presenceActivity.Meta;
            }
        }
        catch (Exception ex)
        {
            // On error, remove the presence to avoid stale data
            await RemoveSpotifyPresenceAsync(account.Id);

            // In a real implementation, you might want to log the error
            logger.LogError(ex, "Failed to update Spotify presence for user {UserId}", account.Id);
        }
    }

    /// <summary>
    /// Removes the Spotify presence activity for a user
    /// </summary>
    private async Task RemoveSpotifyPresenceAsync(Guid accountId)
    {
        await accountEventService.UpdateActivityByManualId(
            "spotify",
            accountId,
            activity =>
            {
                // Mark it for immediate expiration
                activity.LeaseExpiresAt = SystemClock.Instance.GetCurrentInstant();
            }
        );
    }

    private static SnPresenceActivity ParseCurrentlyPlayingToPresenceActivity(Guid accountId, CurrentlyPlaying currentlyPlaying)
    {
        // Cast the item to FullTrack (it should be a track for music presence)
        if (currentlyPlaying.Item is not FullTrack track)
        {
            throw new InvalidOperationException("Currently playing item is not a track");
        }

        // Get track name
        var trackName = track.Name ?? "";
        if (string.IsNullOrEmpty(trackName))
        {
            throw new InvalidOperationException("Track name not available");
        }

        // Get artists
        var artists = track.Artists ?? new List<SimpleArtist>();
        var artistNames = artists.Select(a => a.Name).Where(name => !string.IsNullOrEmpty(name)).ToList();
        var artistsString = string.Join(", ", artistNames);

        // Get artist URLs
        var artistsUrls = artists
            .Where(a => a.ExternalUrls?.ContainsKey("spotify") == true)
            .Select(a => a.ExternalUrls!["spotify"])
            .ToList();

        // Get album info
        var album = track.Album;
        var albumName = album?.Name ?? "";
        string? albumImageUrl = null;

        // Get largest album image
        if (album?.Images != null && album.Images.Count > 0)
        {
            albumImageUrl = album.Images
                .OrderByDescending(img => img.Width)
                .FirstOrDefault()?.Url;
        }

        // Get track URL
        string? trackUrl = null;
        if (track.ExternalUrls?.ContainsKey("spotify") == true)
        {
            trackUrl = track.ExternalUrls["spotify"];
        }

        // Get progress and duration
        var progressMs = currentlyPlaying.ProgressMs ?? 0;
        var durationMs = track.DurationMs;
        var progressPercent = durationMs > 0 ? (double)progressMs / durationMs * 100 : 0;

        // Get context info
        var contextType = currentlyPlaying.Context?.Type;
        string? contextUrl = null;
        if (currentlyPlaying.Context?.ExternalUrls?.ContainsKey("spotify") == true)
        {
            contextUrl = currentlyPlaying.Context.ExternalUrls["spotify"];
        }

        // Build metadata
        var meta = new Dictionary<string, object>
        {
            ["track_duration_ms"] = durationMs,
            ["progress_ms"] = progressMs,
            ["progress_percent"] = progressPercent,
            ["spotify_track_url"] = trackUrl,
            ["updated_at"] = SystemClock.Instance.GetCurrentInstant()
        };

        // Add track ID
        if (!string.IsNullOrEmpty(track.Id))
            meta["track_id"] = track.Id;

        // Add album ID
        if (!string.IsNullOrEmpty(album?.Id))
            meta["album_id"] = album.Id;

        // Add artist IDs
        var artistIds = artists.Select(a => a.Id).Where(id => !string.IsNullOrEmpty(id)).ToArray();
        meta["artist_ids"] = artistIds;

        // Add context info
        if (!string.IsNullOrEmpty(contextType))
            meta["context_type"] = contextType;
        if (!string.IsNullOrEmpty(contextUrl))
            meta["context_url"] = contextUrl;

        // Add track properties
        meta["is_explicit"] = track.Explicit;
        meta["popularity"] = track.Popularity;

        return new SnPresenceActivity
        {
            AccountId = accountId,
            Type = PresenceType.Music,
            ManualId = "spotify",
            Title = trackName,
            Subtitle = artistsString,
            Caption = albumName,
            LargeImage = albumImageUrl,
            TitleUrl = trackUrl,
            SubtitleUrl = artistsUrls.FirstOrDefault(),
            Meta = meta
        };
    }
}
