/**
 * Generated by Claude Sonnet 4.5 2025⁄10⁄21
 */

import * as fs from 'fs'
import * as https from 'https'
import * as dotenv from 'dotenv'
import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'

// Load environment variables from .env file
dotenv.config()

interface SpotifyTrack {
  name: string
  artists: string[]
  album: string
  duration_ms: number
  uri: string
  external_url: string
}

interface PlaylistExport {
  name: string
  description: string
  total_tracks: number
  tracks: SpotifyTrack[]
}

class SpotifyPlaylistExporter {
  private accessToken: string
  private clientId: string
  private clientSecret: string

  constructor(clientId: string, clientSecret: string) {
    this.clientId = clientId
    this.clientSecret = clientSecret
    this.accessToken = ''
  }

  /**
   * Authenticate with Spotify using Client Credentials flow
   */
  async authenticate(): Promise<void> {
    const auth = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64')
    const data = 'grant_type=client_credentials'

    return new Promise((resolve, reject) => {
      const options = {
        hostname: 'accounts.spotify.com',
        path: '/api/token',
        method: 'POST',
        headers: {
          'Authorization': `Basic ${auth}`,
          'Content-Type': 'application/x-www-form-urlencoded',
          'Content-Length': data.length
        }
      }

      const req = https.request(options, (res) => {
        let body = ''
        res.on('data', (chunk) => body += chunk)
        res.on('end', () => {
          if (res.statusCode === 200) {
            const response = JSON.parse(body)
            this.accessToken = response.access_token
            console.log('Successfully authenticated with Spotify')
            resolve()
          } else {
            reject(new Error(`Authentication failed: ${res.statusCode} ${body}`))
          }
        })
      })

      req.on('error', reject)
      req.write(data)
      req.end()
    })
  }

  /**
   * Make an API request to Spotify
   */
  private async apiRequest(endpoint: string): Promise<any> {
    return new Promise((resolve, reject) => {
      const options = {
        hostname: 'api.spotify.com',
        path: endpoint,
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${this.accessToken}`
        }
      }

      const req = https.request(options, (res) => {
        let body = ''
        res.on('data', (chunk) => body += chunk)
        res.on('end', () => {
          if (res.statusCode === 200) {
            resolve(JSON.parse(body))
          } else {
            reject(new Error(`API request failed: ${res.statusCode} ${body}`))
          }
        })
      })

      req.on('error', reject)
      req.end()
    })
  }

  /**
   * Fetch all tracks from a playlist (handles pagination)
   */
  async getPlaylistTracks(playlistId: string): Promise<SpotifyTrack[]> {
    const tracks: SpotifyTrack[] = []
    let offset = 0
    const limit = 100
    let hasMore = true

    while (hasMore) {
      const endpoint = `/v1/playlists/${playlistId}/tracks?offset=${offset}&limit=${limit}`
      const response = await this.apiRequest(endpoint)

      for (const item of response.items) {
        if (item.track) {
          tracks.push({
            name: item.track.name,
            artists: item.track.artists.map((artist: any) => artist.name),
            album: item.track.album.name,
            duration_ms: item.track.duration_ms,
            uri: item.track.uri,
            external_url: item.track.external_urls.spotify
          })
        }
      }

      offset += limit
      hasMore = response.next !== null
    }

    return tracks
  }

  /**
   * Get playlist metadata and all tracks
   */
  async exportPlaylist(playlistId: string): Promise<PlaylistExport> {
    console.log(`Fetching playlist: ${playlistId}`)

    const playlistData = await this.apiRequest(`/v1/playlists/${playlistId}`)
    const tracks = await this.getPlaylistTracks(playlistId)

    return {
      name: playlistData.name,
      description: playlistData.description || '',
      total_tracks: tracks.length,
      tracks: tracks
    }
  }

  /**
   * Export playlist to JSON file
   */
  async exportToJSON(playlistId: string, outputPath: string): Promise<void> {
    const playlist = await this.exportPlaylist(playlistId)
    fs.writeFileSync(outputPath, JSON.stringify(playlist, null, 2))
    console.log(`Playlist exported to ${outputPath}`)
  }

  /**
   * Export playlist to CSV file
   */
  async exportToCSV(playlistId: string, outputPath: string): Promise<void> {
    const playlist = await this.exportPlaylist(playlistId)

    const headers = ['Track Name', 'Artists', 'Album', 'Duration (ms)', 'Spotify URI', 'URL']
    const rows = playlist.tracks.map(track => [
      `"${track.name.replace(/"/g, '""')}"`,
      `"${track.artists.join(', ').replace(/"/g, '""')}"`,
      `"${track.album.replace(/"/g, '""')}"`,
      track.duration_ms.toString(),
      track.uri,
      track.external_url
    ])

    const csv = [headers.join(','), ...rows.map(row => row.join(','))].join('\n')
    fs.writeFileSync(outputPath, csv)
    console.log(`Playlist exported to ${outputPath}`)
  }
}

/**
 * Main execution
 */
async function main() {
  // Parse command line arguments with yargs
  const argv = await yargs(hideBin(process.argv))
    .command('$0 <playlist>', 'Export a Spotify playlist', (yargs) => (
      yargs
      .positional('playlist', {
        describe: 'Spotify playlist ID to export',
        type: 'string',
        demandOption: true
      })
    ))
    .option('format', {
      alias: 'f',
      type: 'string',
      choices: ['json', 'csv', 'both'],
      default: 'both',
      description: 'Output format'
    })
    .option('output', {
      alias: 'o',
      type: 'string',
      description: 'Output filename (without extension)',
      default: undefined
    })
    .example('$0 37i9dQZF1DXcBWIGoYBM5M', 'Export playlist to both JSON and CSV')
    .example('$0 37i9dQZF1DXcBWIGoYBM5M --format json', 'Export only to JSON')
    .example('$0 37i9dQZF1DXcBWIGoYBM5M -o my-playlist', 'Export with custom filename')
    .help()
    .alias('help', 'h')
    .version('1.0.0')
    .alias('version', 'v')
    .argv

  const playlistId = argv.playlist as string
  const format = argv.format
  const outputBase = argv.output || `playlist_${playlistId}`

  // Get credentials from environment variables
  const clientId = process.env.SPOTIFY_CLIENT_ID
  const clientSecret = process.env.SPOTIFY_CLIENT_SECRET

  if (!clientId || !clientSecret) {
    console.error('Error: SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables are required')
    console.error('Create a .env file with your credentials:')
    console.error('  SPOTIFY_CLIENT_ID=your_client_id')
    console.error('  SPOTIFY_CLIENT_SECRET=your_client_secret')
    console.error('\nGet your credentials from: https://developer.spotify.com/dashboard')
    process.exit(1)
  }

  try {
    const exporter = new SpotifyPlaylistExporter(clientId, clientSecret)
    await exporter.authenticate()

    // Export based on format option
    if (format === 'json' || format === 'both') {
      await exporter.exportToJSON(playlistId, `${outputBase}.json`)
    }

    if (format === 'csv' || format === 'both') {
      await exporter.exportToCSV(playlistId, `${outputBase}.csv`)
    }

    console.log('Export completed successfully!')
  } catch (error) {
    console.error('Export failed:', error)
    process.exit(1)
  }
}

// Run if called directly
if (require.main === module) {
  main()
}

export { SpotifyPlaylistExporter, SpotifyTrack, PlaylistExport }