param(
    [string]$ArtistQuery = 'young the giant',
    [string]$SearchQuery = 'house'
)

$ErrorActionPreference = 'Stop'

function Get-LightifyDataDir {
    if ($env:LIGHTIFY_DATA_DIR -and $env:LIGHTIFY_DATA_DIR.Trim()) {
        return $env:LIGHTIFY_DATA_DIR.Trim()
    }
    return Join-Path $env:APPDATA 'Lightify'
}

function Read-JsonFile([string]$Path) {
    if (-not (Test-Path $Path)) {
        throw "Missing file: $Path"
    }
    return Get-Content $Path -Raw | ConvertFrom-Json
}

function Save-JsonFile([string]$Path, $Value) {
    $Value | ConvertTo-Json -Depth 8 | Set-Content -Path $Path -Encoding UTF8
}

function Get-UnixNow {
    return [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
}

function Normalize-ArtistName([string]$Name) {
    return (($Name -replace '"', ' ') -split '\s+' | Where-Object { $_ }) -join ' '
}

function Get-TokenHeaders($TokenInfo) {
    return @{ Authorization = "Bearer $($TokenInfo.access_token)" }
}

function Refresh-AccessToken($TokenPath, $ConfigPath, $TokenInfo) {
    $config = Read-JsonFile $ConfigPath
    if (-not $config.client_id) {
        throw 'Cannot refresh token without client_id in lightify_config.json'
    }
    if (-not $TokenInfo.refresh_token) {
        throw 'Cannot refresh token without refresh_token in .lightify_cache'
    }

    $body = @{
        grant_type = 'refresh_token'
        refresh_token = $TokenInfo.refresh_token
        client_id = $config.client_id
    }

    $response = Invoke-RestMethod -Method Post -Uri 'https://accounts.spotify.com/api/token' -Body $body
    $TokenInfo.access_token = $response.access_token
    $TokenInfo.expires_in = if ($null -ne $response.expires_in) { [int64]$response.expires_in } else { 3600 }
    if ($response.refresh_token) {
        $TokenInfo.refresh_token = $response.refresh_token
    }
    $TokenInfo.token_type = if ($response.token_type) { $response.token_type } else { 'Bearer' }
    $TokenInfo.scope = if ($response.scope) { $response.scope } else { $TokenInfo.scope }
    $TokenInfo.expires_at = (Get-UnixNow) + $TokenInfo.expires_in
    Save-JsonFile $TokenPath $TokenInfo
    return $TokenInfo
}

function Invoke-SpotifyJson([string]$Uri, $Headers) {
    return Invoke-RestMethod -Headers $Headers -Uri $Uri
}

function Invoke-SpotifyStatus([string]$Uri, $Headers) {
    try {
        $response = Invoke-WebRequest -Headers $Headers -Uri $Uri -ErrorAction Stop
        return [pscustomobject]@{ Status = [int]$response.StatusCode; Body = $response.Content }
    } catch {
        $status = 0
        if ($_.Exception.Response) {
            $status = [int]$_.Exception.Response.StatusCode.value__
        }
        return [pscustomobject]@{ Status = $status; Body = $_.ErrorDetails.Message }
    }
}

function Write-Result([string]$Name, [bool]$Passed, [string]$Detail) {
    $prefix = if ($Passed) { 'PASS' } else { 'FAIL' }
    Write-Host ("[$prefix] $Name - $Detail")
}

$dataDir = Get-LightifyDataDir
$tokenPath = Join-Path $dataDir '.lightify_cache'
$configPath = Join-Path $dataDir 'lightify_config.json'

$token = Read-JsonFile $tokenPath
if ([int64]$token.expires_at -le ((Get-UnixNow) + 60)) {
    $token = Refresh-AccessToken $tokenPath $configPath $token
}

$headers = Get-TokenHeaders $token
$failures = New-Object System.Collections.Generic.List[string]

$me = Invoke-SpotifyJson 'https://api.spotify.com/v1/me' $headers
$playlists = Invoke-SpotifyJson 'https://api.spotify.com/v1/me/playlists?limit=50' $headers
$playlistItems = @($playlists.items)
$meName = if ($me.display_name) { $me.display_name } else { $me.id }
if ($playlistItems.Count -gt 0) {
    Write-Result 'library-playlists' $true ("loaded $($playlistItems.Count) playlists for $meName")
} else {
    Write-Result 'library-playlists' $false 'no playlists returned'
    $failures.Add('library-playlists') | Out-Null
}

$nonOwned = $playlistItems | Where-Object {
    $ownerId = $_.owner.id
    $ownerName = $_.owner.display_name
    $ownerId -ne $me.id -and $ownerName -ne $me.display_name
} | Select-Object -First 1

if ($nonOwned) {
    $nonOwnedOwner = if ($nonOwned.owner.display_name) { $nonOwned.owner.display_name } else { $nonOwned.owner.id }
    $playlistApi = Invoke-SpotifyStatus ("https://api.spotify.com/v1/playlists/{0}/items?limit=5" -f $nonOwned.id) $headers
    if ($playlistApi.Status -eq 200) {
        Write-Result 'library-nonowned-open' $true ("API opened '$($nonOwned.name)' by $nonOwnedOwner")
    } elseif ($playlistApi.Status -eq 403 -or $playlistApi.Status -eq 404) {
        $html = Invoke-WebRequest -Uri ("https://open.spotify.com/playlist/{0}" -f $nonOwned.id) -ErrorAction Stop
        $trackMatches = [regex]::Matches($html.Content, 'spotify:track:[A-Za-z0-9]{22}') | Select-Object -ExpandProperty Value -Unique
        if ($trackMatches.Count -gt 0) {
            Write-Result 'library-nonowned-open' $true ("web fallback exposed $($trackMatches.Count) track URIs for '$($nonOwned.name)'")
        } else {
            Write-Result 'library-nonowned-open' $false ("403/404 from API and no track URIs in open.spotify HTML for '$($nonOwned.name)'")
            $failures.Add('library-nonowned-open') | Out-Null
        }
    } else {
        Write-Result 'library-nonowned-open' $false ("unexpected status $($playlistApi.Status) for '$($nonOwned.name)'")
        $failures.Add('library-nonowned-open') | Out-Null
    }
} else {
    Write-Result 'library-nonowned-open' $true 'no non-owned playlist available in first page; skipped'
}

$search = Invoke-SpotifyJson ("https://api.spotify.com/v1/search?q={0}&type=track,playlist,artist,album&limit=5" -f [uri]::EscapeDataString($SearchQuery)) $headers
$searchOk = @($search.tracks.items).Count -gt 0 -and @($search.albums.items).Count -gt 0 -and @($search.playlists.items).Count -gt 0 -and @($search.artists.items).Count -gt 0
if ($searchOk) {
    Write-Result 'search-surface' $true ("query '$SearchQuery' returned tracks/albums/playlists/artists")
} else {
    Write-Result 'search-surface' $false ("query '$SearchQuery' did not return all result classes")
    $failures.Add('search-surface') | Out-Null
}

$artistSearch = Invoke-SpotifyJson ("https://api.spotify.com/v1/search?q={0}&type=artist&limit=1" -f [uri]::EscapeDataString($ArtistQuery)) $headers
$artist = @($artistSearch.artists.items) | Select-Object -First 1
if (-not $artist) {
    Write-Result 'artist-fallback' $false ("no artist found for '$ArtistQuery'")
    $failures.Add('artist-fallback') | Out-Null
} else {
    $artistName = Normalize-ArtistName $artist.name
    $topTracks = Invoke-SpotifyStatus ("https://api.spotify.com/v1/artists/{0}/top-tracks?market=from_token" -f $artist.id) $headers
    if ($topTracks.Status -eq 200) {
        Write-Result 'artist-fallback' $true ("top-tracks API succeeded for '$($artist.name)'")
    } elseif ($topTracks.Status -eq 403) {
        $artistTrackQuery = 'artist:"' + $artistName + '"'
        $matches = @()
        foreach ($offset in 0, 10, 20, 30, 40, 50, 60, 70, 80, 90) {
            $trackSearch = Invoke-SpotifyJson ("https://api.spotify.com/v1/search?q={0}&type=track&limit=10&offset={1}" -f [uri]::EscapeDataString($artistTrackQuery), $offset) $headers
            $items = @($trackSearch.tracks.items)
            if ($items.Count -eq 0) {
                break
            }
            $matches += $items | Where-Object {
                @($_.artists) | Where-Object {
                    $_.id -eq $artist.id -or (Normalize-ArtistName $_.name).ToLowerInvariant() -eq $artistName.ToLowerInvariant()
                }
            }
            if (-not $trackSearch.tracks.next) {
                break
            }
        }
        if ($matches.Count -gt 0) {
            Write-Result 'artist-fallback' $true ("search fallback found $($matches.Count) tracks for '$($artist.name)' after top-tracks 403")
        } else {
            Write-Result 'artist-fallback' $false ("top-tracks 403 and search fallback found no tracks for '$($artist.name)'")
            $failures.Add('artist-fallback') | Out-Null
        }
    } else {
        Write-Result 'artist-fallback' $false ("unexpected top-tracks status $($topTracks.Status) for '$($artist.name)'")
        $failures.Add('artist-fallback') | Out-Null
    }
}

if ($failures.Count -gt 0) {
    throw ("Smoke test failures: " + ($failures -join ', '))
}

Write-Host '[PASS] smoke-suite - all checks passed'