import { ApiError } from '../utils/ApiError.js';

const TOKEN_URL = 'https://accounts.spotify.com/api/token';
const API_BASE = 'https://api.spotify.com/v1';

let cachedToken = null;
let tokenExpiresAt = 0;

/**
 * Client Credentials flow. Suitable here because we only read public catalog
 * data (no user-specific scopes needed).
 */
async function getAccessToken() {
  if (cachedToken && Date.now() < tokenExpiresAt - 10_000) {
    return cachedToken;
  }

  const clientId = process.env.SPOTIFY_CLIENT_ID;
  const clientSecret = process.env.SPOTIFY_CLIENT_SECRET;

  if (!clientId || !clientSecret) {
    throw new ApiError(500, 'Spotify credentials are not configured on the server');
  }

  const basic = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');

  const res = await fetch(TOKEN_URL, {
    method: 'POST',
    headers: {
      Authorization: `Basic ${basic}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: 'grant_type=client_credentials',
  });

  if (!res.ok) {
    throw new ApiError(502, 'Failed to authenticate with Spotify');
  }

  const data = await res.json();
  cachedToken = data.access_token;
  tokenExpiresAt = Date.now() + data.expires_in * 1000;
  return cachedToken;
}

/**
 * Accepts any common Spotify track link/URI format and returns the bare track ID.
 * Supports:
 *  - https://open.spotify.com/track/{id}?si=...
 *  - https://open.spotify.com/intl-xx/track/{id}
 *  - spotify:track:{id}
 */
export function extractSpotifyTrackId(input) {
  if (!input) return null;
  const trimmed = input.trim();

  const uriMatch = trimmed.match(/^spotify:track:([a-zA-Z0-9]+)$/);
  if (uriMatch) return uriMatch[1];

  try {
    const url = new URL(trimmed);
    const parts = url.pathname.split('/').filter(Boolean);
    const trackIndex = parts.indexOf('track');
    if (trackIndex !== -1 && parts[trackIndex + 1]) {
      return parts[trackIndex + 1];
    }
  } catch {
    // not a valid URL — fall through
  }

  return null;
}

/**
 * Same idea as extractSpotifyTrackId, for album links/URIs. Used to offer a
 * track picker instead of erroring when an admin pastes an album link — see
 * fetchSpotifyAlbum below.
 */
export function extractSpotifyAlbumId(input) {
  if (!input) return null;
  const trimmed = input.trim();

  const uriMatch = trimmed.match(/^spotify:album:([a-zA-Z0-9]+)$/);
  if (uriMatch) return uriMatch[1];

  try {
    const url = new URL(trimmed);
    const parts = url.pathname.split('/').filter(Boolean);
    const albumIndex = parts.indexOf('album');
    if (albumIndex !== -1 && parts[albumIndex + 1]) {
      return parts[albumIndex + 1];
    }
  } catch {
    // not a valid URL — fall through
  }

  return null;
}

const OTHER_SPOTIFY_ENTITY_TYPES = ['album', 'playlist', 'artist', 'episode', 'show'];

/**
 * When extractSpotifyTrackId comes up empty, this tells apart "that's a
 * Spotify link, just not to a track" (album/playlist/artist/episode/show)
 * from "not a Spotify link at all" (garbage input, or a bare track ID
 * pasted without its URL) — so the caller can give a specific error instead
 * of a misleading "track not found" once it hits the Spotify API.
 */
export function detectNonTrackSpotifyLinkType(input) {
  if (!input) return null;
  const trimmed = input.trim();

  const uriMatch = trimmed.match(/^spotify:([a-z]+):/);
  if (uriMatch) return OTHER_SPOTIFY_ENTITY_TYPES.includes(uriMatch[1]) ? uriMatch[1] : null;

  try {
    const url = new URL(trimmed);
    if (!url.hostname.endsWith('spotify.com')) return null;
    const parts = url.pathname.split('/').filter(Boolean);
    return parts.find((p) => OTHER_SPOTIFY_ENTITY_TYPES.includes(p)) ?? null;
  } catch {
    return null;
  }
}

export async function fetchSpotifyTrack(trackId) {
  const token = await getAccessToken();

  const res = await fetch(`${API_BASE}/tracks/${trackId}`, {
    headers: { Authorization: `Bearer ${token}` },
  });

  if (res.status === 404) {
    throw new ApiError(404, 'Spotify track not found. Check the URL and try again.');
  }
  if (!res.ok) {
    throw new ApiError(502, 'Failed to fetch track metadata from Spotify');
  }

  const track = await res.json();

  const artwork = track.album?.images?.[0]?.url ?? null;
  if (!artwork) {
    throw new ApiError(422, 'Spotify track has no artwork available');
  }

  return {
    title: track.name,
    artist: track.artists.map((a) => a.name).join(', '),
    album: track.album?.name ?? null,
    artwork_url: artwork,
    spotify_track_id: track.id,
    spotify_url: track.external_urls?.spotify ?? `https://open.spotify.com/track/${track.id}`,
    duration_ms: track.duration_ms,
  };
}

/**
 * Fetches an album's track list so the admin can pick which single track
 * becomes the fanlink — this app is "one song, one page" by design, an
 * album link is just a shortcut to the right track, not a new page type.
 */
export async function fetchSpotifyAlbum(albumId) {
  const token = await getAccessToken();

  const res = await fetch(`${API_BASE}/albums/${albumId}`, {
    headers: { Authorization: `Bearer ${token}` },
  });

  if (res.status === 404) {
    throw new ApiError(404, 'Spotify album not found. Check the URL and try again.');
  }
  if (!res.ok) {
    throw new ApiError(502, 'Failed to fetch album metadata from Spotify');
  }

  const album = await res.json();

  // Most albums fit in one page (default limit 20, max 50), but
  // compilations can run longer — follow pagination to get every track.
  const tracks = [...(album.tracks?.items ?? [])];
  let next = album.tracks?.next;
  while (next) {
    const pageRes = await fetch(next, { headers: { Authorization: `Bearer ${token}` } });
    if (!pageRes.ok) break;
    const page = await pageRes.json();
    tracks.push(...page.items);
    next = page.next;
  }

  return {
    name: album.name,
    artist: album.artists.map((a) => a.name).join(', '),
    artwork_url: album.images?.[0]?.url ?? null,
    tracks: tracks.map((t) => ({
      spotify_track_id: t.id,
      title: t.name,
      artists: t.artists.map((a) => a.name).join(', '),
      track_number: t.track_number,
      duration_ms: t.duration_ms,
    })),
  };
}
