import type {
  SpotifyDevice,
  SpotifyDevicesResponse,
  SpotifyPlaybackState,
  SpotifyPlaybackSnapshot,
  SpotifyError,
  SpotifyTrack,
} from './types';

const SPOTIFY_API_BASE = 'https://api.spotify.com/v1';

export class SpotifyWebApiClient {
  private accessToken: string;

  constructor(accessToken: string) {
    this.accessToken = accessToken;
  }

  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const url = `${SPOTIFY_API_BASE}${endpoint}`;
    
    const response = await fetch(url, {
      ...options,
      headers: {
        'Authorization': `Bearer ${this.accessToken}`,
        'Content-Type': 'application/json',
        ...options.headers,
      },
    });

    if (!response.ok) {
      try {
        const error: SpotifyError = await response.json();
        throw new Error(`Spotify API Error: ${error.error?.message || 'Unknown error'}`);
      } catch (parseError) {
        throw new Error(`Spotify API Error: ${response.status} ${response.statusText}`);
      }
    }

    if (response.status === 204 || response.status === 202) {
      return {} as T;
    }

    const contentLength = response.headers.get('content-length');
    if (contentLength === '0') {
      return {} as T;
    }

    const contentType = response.headers.get('content-type') || '';
    if (!contentType.includes('application/json')) {
      console.warn('Non-JSON response from Spotify API:', contentType, response.status);
      return {} as T;
    }

    try {
      return await response.json();
    } catch (error) {
      console.warn('Failed to parse Spotify API JSON response:', error);
      return {} as T;
    }
  }

  async getDevices(): Promise<SpotifyDevice[]> {
    const response = await this.request<SpotifyDevicesResponse>('/me/player/devices');
    return response.devices;
  }

  async transferPlayback(deviceId: string, play: boolean = false): Promise<void> {
    await this.request('/me/player', {
      method: 'PUT',
      body: JSON.stringify({
        device_ids: [deviceId],
        play,
      }),
    });
  }

  async getPlaybackState(): Promise<SpotifyPlaybackState | null> {
    try {
      return await this.request<SpotifyPlaybackState>('/me/player');
    } catch (error) {
      if (error instanceof Error && error.message.includes('204')) {
        return null;
      }
      throw error;
    }
  }

  async play(deviceId?: string): Promise<void> {
    const endpoint = deviceId ? `/me/player/play?device_id=${deviceId}` : '/me/player/play';
    await this.request(endpoint, {
      method: 'PUT',
    });
  }

  async playTrack(trackId: string, deviceId?: string, positionMs?: number): Promise<void> {
    const endpoint = deviceId ? `/me/player/play?device_id=${deviceId}` : '/me/player/play';
    await this.request(endpoint, {
      method: 'PUT',
      body: JSON.stringify({
        uris: [`spotify:track:${trackId}`],
        position_ms: positionMs || 0,
      }),
    });
  }

  async pause(): Promise<void> {
    await this.request('/me/player/pause', {
      method: 'PUT',
    });
  }

  async skipToNext(): Promise<void> {
    await this.request('/me/player/next', {
      method: 'POST',
    });
  }

  async skipToPrevious(): Promise<void> {
    await this.request('/me/player/previous', {
      method: 'POST',
    });
  }

  async setVolume(volumePercent: number): Promise<void> {
    await this.request(`/me/player/volume?volume_percent=${volumePercent}`, {
      method: 'PUT',
    });
  }

  async getCurrentUser(): Promise<{ id: string; display_name: string }> {
    return this.request<{ id: string; display_name: string }>('/me');
  }

  async searchTracks(query: string, limit: number = 20): Promise<{ tracks: { items: SpotifyTrack[] } }> {
    const encodedQuery = encodeURIComponent(query);
    return this.request<{ tracks: { items: SpotifyTrack[] } }>(`/search?q=${encodedQuery}&type=track&limit=${limit}`);
  }

  updateAccessToken(accessToken: string): void {
    this.accessToken = accessToken;
  }

  async createPlaybackSnapshot(): Promise<SpotifyPlaybackSnapshot | null> {
    const state = await this.getPlaybackState();
    if (!state || !state.item) {
      return null;
    }

    const snapshot: SpotifyPlaybackSnapshot = {
      contextUri: state.context?.uri,
      trackUri: state.item.external_urls.spotify,
      trackId: state.item.id,
      positionMs: state.progress_ms || 0,
      shuffleState: state.shuffle_state,
      repeatState: state.repeat_state,
      volumePercent: state.device.volume_percent || undefined,
      deviceId: state.device.id,
      timestamp: Date.now(),
      isPlaying: state.is_playing,
      trackName: state.item.name,
      artistName: state.item.artists.map(a => a.name).join(', '),
      albumName: state.item.album.name,
      albumImageUrl: state.item.album.images[2]?.url,
    };

    return snapshot;
  }

  async restorePlaybackSnapshot(snapshot: SpotifyPlaybackSnapshot): Promise<void> {
    if (snapshot.deviceId) {
      await this.transferPlayback(snapshot.deviceId, false);
      await new Promise(resolve => setTimeout(resolve, 500));
    }

    const playbackRequest: any = {
      position_ms: snapshot.positionMs,
    };

    if (snapshot.contextUri && snapshot.trackId) {
      playbackRequest.context_uri = snapshot.contextUri;
      playbackRequest.offset = { uri: `spotify:track:${snapshot.trackId}` };
    } else if (snapshot.trackId) {
      playbackRequest.uris = [`spotify:track:${snapshot.trackId}`];
    }

    const endpoint = snapshot.deviceId 
      ? `/me/player/play?device_id=${snapshot.deviceId}`
      : '/me/player/play';
    
    await this.request(endpoint, {
      method: 'PUT',
      body: JSON.stringify(playbackRequest),
    });

    await Promise.all([
      this.request(`/me/player/shuffle?state=${snapshot.shuffleState}`, {
        method: 'PUT',
      }),
      this.request(`/me/player/repeat?state=${snapshot.repeatState}`, {
        method: 'PUT',
      }),
      snapshot.volumePercent !== undefined 
        ? this.setVolume(snapshot.volumePercent)
        : Promise.resolve(),
    ]);

    if (!snapshot.isPlaying) {
      await new Promise(resolve => setTimeout(resolve, 1000));
      await this.pause();
    }
  }
}