using SpotifyAPI.Web;
using System.Web;
using Microsoft.EntityFrameworkCore;

public class SpotifyApiClient
{
    private SpotifyClient _spotifyClient;
    private SpotifyClient? _userAuthorizedSpotifyClient;
    private ILogger<SpotifyApiClient> _logger;

    public SpotifyApiClient(ILogger<SpotifyApiClient> logger)
    {
        var config = SpotifyClientConfig.CreateDefault()
            .WithAuthenticator(new ClientCredentialsAuthenticator(
                AppConfiguration.Instance.SpotifyClientId,
                AppConfiguration.Instance.SpotifyClientSecret));

        _spotifyClient = new SpotifyClient(config);
        _userAuthorizedSpotifyClient = null;
        _logger = logger;
    }

    public async Task<SpotifyApiClient> WithUserAuthorizationAsync(User user)
    {
        var refreshResponse = await new OAuthClient().RequestToken(
            new AuthorizationCodeRefreshRequest(
                            AppConfiguration.Instance.SpotifyClientId,
                            AppConfiguration.Instance.SpotifyClientSecret,
                            user.SpotifyAuthRefreshToken)
        );
        var config = SpotifyClientConfig
            .CreateDefault()
            .WithAuthenticator(new AuthorizationCodeAuthenticator(
                            AppConfiguration.Instance.SpotifyClientId,
                            AppConfiguration.Instance.SpotifyClientSecret,
                            new AuthorizationCodeTokenResponse()
                            {
                                RefreshToken = refreshResponse.RefreshToken,
                                AccessToken = refreshResponse.AccessToken,
                                TokenType = refreshResponse.TokenType,
                                ExpiresIn = refreshResponse.ExpiresIn,
                                Scope = refreshResponse.Scope,
                                CreatedAt = refreshResponse.CreatedAt
                            }));

        _userAuthorizedSpotifyClient = new SpotifyClient(config);
        return this;
    }

    private SpotifyClient UserAuthorizedSpotifyClient
    {
        get
        {
            if (_userAuthorizedSpotifyClient == null)
            {
                throw new Exception("Cannot perform Spotify API call without user authorization. Authorize Spotify access from your user page first!");
            }
            return _userAuthorizedSpotifyClient;
        }
    }

    public async Task<List<FullTrack>> GetTrackCandidatesAsync(string trackName, string artistName)
    {
        try
        {
            var searchResponse = await _spotifyClient.Search.Item(new SearchRequest(SearchRequest.Types.Track, $"{trackName} {artistName}")
            {
                Limit = 5
            });
            return searchResponse.Tracks.Items ?? new List<FullTrack>();
        }
        catch (APIException ex)
        {
            throw new Exception($"Error fetching tracks by query: \"{trackName} {artistName}\": {ex.Message}", ex);
        }
    }

    public string GetLoginRedirectUri()
    {
        return AppConfiguration.Instance.WebUIBaseURL + (AppConfiguration.Instance.WebUIBaseURL.EndsWith("/") ? "SpotifyLogin" : "/SpotifyLogin");
    }

    private bool IsAuthTokenExpired(User user)
    {
        if (user.SpotifyAuthCreatedAt == null || user.SpotifyAuthExpiresAfterSeconds == null)
        {
            return true;
        }
        var expirationdate = user.SpotifyAuthCreatedAt.Value.AddSeconds(user.SpotifyAuthExpiresAfterSeconds.Value);
        return expirationdate < DateTime.UtcNow;
    }

    public async Task DeAuthorizeUserAsync(User user)
    {
        using (var dci = DataContext.Instance)
        {
            var isEntityTracked = dci.Entry(user).State != EntityState.Detached;

            if (!isEntityTracked)
            {
                user = dci.Users.Find(user.UserId);
            }
            user.SpotifyAuthAccessToken = string.Empty;
            user.SpotifyAuthRefreshToken = string.Empty;
            user.SpotifyAuthExpiresAfterSeconds = null;
            user.SpotifyAuthCreatedAt = null;
            await dci.SaveChangesAsync();
        }
    }

    public async Task<string> GetValidAuthorizationTokenAsync(User user)
    {
        if (string.IsNullOrEmpty(user.SpotifyAuthAccessToken))
        {
            // user either never connected Spotify or we failed to refresh token - user needs to re-authenticate
            return string.Empty;
        }

        if (!this.IsAuthTokenExpired(user))
        {
            return user.SpotifyAuthAccessToken;
        }

        // if token is expired, attempt a refresh
        var dci = DataContext.Instance;

        var isEntityTracked = dci.Entry(user).State != EntityState.Detached;

        if (!isEntityTracked)
        {
            user = dci.Users.Find(user.UserId);
        }

        try
        {
            var oAuthResponse = await new OAuthClient().RequestToken(
                new AuthorizationCodeRefreshRequest(AppConfiguration.Instance.SpotifyClientId, AppConfiguration.Instance.SpotifyClientSecret, user.SpotifyAuthRefreshToken)
            );
            user.SpotifyAuthAccessToken = oAuthResponse.AccessToken;
            user.SpotifyAuthExpiresAfterSeconds = oAuthResponse.ExpiresIn;
            user.SpotifyAuthCreatedAt = oAuthResponse.CreatedAt;
            user.SpotifyAuthRefreshToken = oAuthResponse.RefreshToken;
            return user.SpotifyAuthAccessToken;
        }
        catch (Exception ex)
        {
            _logger.LogWarning($"Failed to refresh SpotifyAuth token for user {user.LdapUserName}: {ex.Message}");
            await DeAuthorizeUserAsync(user);
            return string.Empty;
        }
        finally
        {
            await dci.SaveChangesAsync();
            dci.Dispose();
        }
    }

    public async Task<bool> IsUserAuthenticatedAsync(User user)
    {
        return !string.IsNullOrEmpty(await this.GetValidAuthorizationTokenAsync(user));
    }

    public async Task<FullTrack> GetTrackByIdAsync(string trackId)
    {
        try
        {
            return await _spotifyClient.Tracks.Get(trackId);
        }
        catch (APIException ex)
        {
            throw new Exception($"Error fetching track by ID: {trackId}: {ex.Message}", ex);
        }
    }

    public async Task<SmartPlaylistDefinition> CreateSpotifyPlaylist(string playlistTitle,
                                                                    string description,
                                                                    bool IncludesUnCategorizedSongs,
                                                                    bool IncludesLikedSongs,
                                                                    User createdBy)
    {
        try
        {
            // for now hardcoded with my user ID
            var playlistCreationRequest = new PlaylistCreateRequest(playlistTitle);
            playlistCreationRequest.Public = true;
            playlistCreationRequest.Collaborative = false;
            playlistCreationRequest.Description = description;
            var currentUser = await UserAuthorizedSpotifyClient.UserProfile.Current();
            var playlist = await UserAuthorizedSpotifyClient.Playlists.Create(currentUser.Id, playlistCreationRequest);

            _logger.LogWarning($"Creating new playlist '{playlistTitle}'");
            using (var dci = DataContext.Instance)
            {
                var trackedUserEntity = dci.Users.Find(createdBy.UserId);
                var newPlaylist = new SmartPlaylistDefinition()
                {
                    Title = playlistTitle,
                    Description = description,
                    Categories = new List<SuggestionHelper>(),
                    IncludesLikedSongs = IncludesLikedSongs,
                    IncludesUnCategorizedSongs = IncludesUnCategorizedSongs,
                    SpotifyPlaylistId = playlist.Id,
                    CreatedBy = trackedUserEntity,
                    ExplicitlyExcludedSongs = new List<Song>(),
                    ExplicitlyIncludedSongs = new List<Song>()
                };

                var trackedEntity = dci.SmartPlaylistDefinitions.Add(newPlaylist);
                await dci.SaveChangesAsync();
                return trackedEntity.Entity;
            }
        }
        catch (APIException ex)
        {
            throw new Exception($"Error creating playlist with title: {playlistTitle}: {ex.Message}", ex);
        }
    }

    public async Task<List<string>> GetSongsInPlaylist(string playlistId)
    {
        try
        {
            // for now hardcoded with my user ID
            var ids = new List<string>();
            var firstContentPage = await UserAuthorizedSpotifyClient.Playlists.GetItems(playlistId);
            var allPages = await UserAuthorizedSpotifyClient.PaginateAll(firstContentPage);
            ids.AddRange(allPages.Select(track => (track.Track as FullTrack).Id));

            return ids;

        }
        catch (APIException ex)
        {
            throw new Exception($"Error fetching playlist contents for playlist with id: {playlistId}: {ex.Message}", ex);
        }
    }

    public async Task<string> AddSongsToPlaylist(string playlistId, List<string> songIds)
    {
        if (songIds.Count == 0)
        {
            _logger.LogWarning($"No songs to add to playlist with id '{playlistId}'");
            return string.Empty;
        }
        try
        {
            // for now hardcoded with my user ID
            var addItemRequest = new PlaylistAddItemsRequest(songIds.Select(id => $"spotify:track:{id}").ToList());
            _logger.LogWarning($"Adding songs to playlist with id '{playlistId}'");
            var response = await UserAuthorizedSpotifyClient.Playlists.AddItems(playlistId, addItemRequest);
            return response.SnapshotId;

        }
        catch (APIException ex)
        {
            throw new Exception($"Error adding songs to playlist with id: {playlistId}: {ex.Message}", ex);
        }
    }

    public async Task<string> RemoveSongsFromPlaylist(string playlistId, List<string> songIds)
    {
        if (songIds.Count == 0)
        {
            _logger.LogWarning($"No songs to remove from playlist with id '{playlistId}'");
            return string.Empty;
        }
        try
        {
            // for now hardcoded with my user ID
            var removeItemsRequest = new PlaylistRemoveItemsRequest();
            removeItemsRequest.Tracks = new List<PlaylistRemoveItemsRequest.Item>();
            foreach (var song in songIds)
            {
                var item = new PlaylistRemoveItemsRequest.Item()
                {
                    Uri = $"spotify:track:{song}",
                };
                removeItemsRequest.Tracks.Add(item);
            }

            _logger.LogWarning($"Removing songs from playlist with id '{playlistId}'");
            var response = await UserAuthorizedSpotifyClient.Playlists.RemoveItems(playlistId, removeItemsRequest);
            return response.SnapshotId;
        }
        catch (APIException ex)
        {
            throw new Exception($"Error removing songs from playlist with id: {playlistId}: {ex.Message}", ex);
        }
    }
}