import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PlaylistEntity } from './playlist.entity';
import { TrackService } from '../track/track.service';
import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
import { Server } from 'socket.io';
import * as fs from 'fs';
import { Interval } from '@nestjs/schedule';
import { TrackStatusEnum } from '../track/track.entity';
import { UtilsService } from '../shared/utils.service';
import { SpotifyService } from '../shared/spotify.service';
import { toSafeErrorMessage } from '../shared/errors/safe-error';

enum WsPlaylistOperation {
  New = 'playlistNew',
  Update = 'playlistUpdate',
  Delete = 'playlistDelete',
}

@WebSocketGateway()
@Injectable()
export class PlaylistService {
  @WebSocketServer() io: Server;
  private readonly logger = new Logger(TrackService.name);

  constructor(
    @InjectRepository(PlaylistEntity)
    private repository: Repository<PlaylistEntity>,
    private readonly trackService: TrackService,
    private readonly utilsService: UtilsService,
    private readonly spotifyService: SpotifyService,
  ) {}

  findAll(
    relations: Record<string, boolean> = { tracks: true },
    where?: Partial<PlaylistEntity>,
  ): Promise<PlaylistEntity[]> {
    return this.repository.find({ where, relations });
  }

  findOne(id: number): Promise<PlaylistEntity | null> {
    return this.repository.findOneBy({ id });
  }

  async remove(id: number): Promise<void> {
    await this.repository.delete(id);
    this.io.emit(WsPlaylistOperation.Delete, { id });
  }


  async createFromSearch(
    artist: string,
    title: string,
  ): Promise<{ spotifyUrl: string; name: string; artist: string }> {
    const track = await this.spotifyService.searchTrack(artist, title);

    await this.create({
      spotifyUrl: track.spotifyUrl,
      name: track.name,
      active: false,
      isTrack: true,
    } as PlaylistEntity);

    return {
      spotifyUrl: track.spotifyUrl,
      name: track.name,
      artist: track.artist,
    };
  }

  async create(playlist: PlaylistEntity): Promise<void> {
    // Detect if URL is for a single track or a playlist and route accordingly
    const isTrack = this.spotifyService.isTrackUrl(playlist.spotifyUrl);
    const isArtist = this.spotifyService.isArtistUrl(playlist.spotifyUrl);

    if (isTrack) {
      await this.createSingleTrack(playlist);
    } else if (isArtist) {
      await this.createArtistLibrary(playlist);
    } else {
      await this.createPlaylist(playlist);
    }
  }

  private async createSingleTrack(playlist: PlaylistEntity): Promise<void> {
    let trackDetail: { name: string; artist: string; image: string; durationMs?: number };
    let playlist2Save: PlaylistEntity;
    try {
      trackDetail = await this.spotifyService.getTrackDetail(
        playlist.spotifyUrl,
      );
      this.logger.debug(`Track detail retrieved: ${trackDetail.name}`);

      playlist2Save = {
        ...playlist,
        name: trackDetail.name,
        coverUrl: trackDetail.image,
        isTrack: true,
        active: false, // Single tracks cannot be subscribed
      };
      // Don't create folder structure for individual tracks - they go in root
    } catch (err) {
      this.logger.error(`Error getting track details: ${err}`);
      playlist2Save = {
        ...playlist,
        error: toSafeErrorMessage(err),
        isTrack: true,
      };
    }
    const savedPlaylist = await this.save(playlist2Save);

    if (trackDetail) {
      try {
        await this.trackService.create(
          {
            artist: trackDetail.artist,
            name: trackDetail.name,
            spotifyUrl: playlist.spotifyUrl,
            coverUrl: trackDetail.image,
            durationMs: trackDetail.durationMs,
          },
          savedPlaylist,
        );
      } catch (error) {
        this.logger.error(
          `Error creating track "${trackDetail.artist} - ${trackDetail.name}": ${error.message}`,
        );
      }
    }
  }


  private async createArtistLibrary(playlist: PlaylistEntity): Promise<void> {
    let detail: { tracks: any[]; name: string; image: string };
    let playlist2Save: PlaylistEntity;

    try {
      detail = await this.spotifyService.getArtistLibrary(playlist.spotifyUrl);
      this.logger.debug(
        `Artist library retrieved with ${detail.tracks?.length || 0} tracks`,
      );

      playlist2Save = {
        ...playlist,
        name: detail.name,
        coverUrl: detail.image,
        active: false,
      };
      this.createPlaylistFolderStructure(playlist2Save.name);
    } catch (err) {
      this.logger.error(`Error getting artist library details: ${err}`);
      playlist2Save = {
        ...playlist,
        name: 'Failed artist library import',
        error: toSafeErrorMessage(err),
        active: false,
      };
    }

    const savedPlaylist = await this.save(playlist2Save);

    if (detail?.tracks && detail.tracks.length > 0) {
      await this.createTracksForSavedPlaylist(detail.tracks, savedPlaylist);
    } else {
      this.logger.warn(`No tracks found for artist library ${savedPlaylist.name}`);
    }
  }

  private async createTracksForSavedPlaylist(
    tracks: any[],
    savedPlaylist: PlaylistEntity,
  ): Promise<void> {
    this.logger.debug(
      `Starting to process ${tracks.length} tracks for ${savedPlaylist.name}`,
    );

    let processedCount = 0;
    let skippedCount = 0;
    let errorCount = 0;

    for (const track of tracks) {
      try {
        if (!track.artist || !track.name) {
          this.logger.warn(
            `Skipping track ${processedCount + skippedCount + 1}: Missing artist or name information`,
          );
          skippedCount++;
          continue;
        }

        if (track.unavailable === true) {
          this.logger.warn(
            `Skipping unavailable track ${processedCount + skippedCount + 1}: ${track.artist} - ${track.name}`,
          );
          skippedCount++;
          continue;
        }

        await this.trackService.create(
          {
            artist: track.artist,
            name: track.name,
            spotifyUrl: track.previewUrl || null,
            coverUrl: track.coverUrl || savedPlaylist.coverUrl,
            durationMs: track.durationMs,
          },
          savedPlaylist,
        );

        processedCount++;

        if (processedCount % 100 === 0) {
          this.logger.debug(
            `Processed ${processedCount} tracks so far for ${savedPlaylist.name}`,
          );
        }
      } catch (error) {
        this.logger.error(
          `Error creating track "${
            track?.artist || 'Unknown'
          } - ${track?.name || 'Unknown'}": ${error.message}`,
        );
        errorCount++;
      }
    }

    this.logger.debug(
      `Finished processing ${savedPlaylist.name}: ` +
        `${processedCount} tracks processed, ${skippedCount} skipped, ${errorCount} errors`,
    );
  }

  private async createPlaylist(playlist: PlaylistEntity): Promise<void> {
    let detail: { tracks: any; name: any; image: any };
    let playlist2Save: PlaylistEntity;
    try {
      detail = await this.spotifyService.getPlaylistDetail(playlist.spotifyUrl);
      this.logger.debug(
        `Playlist detail retrieved with ${detail.tracks?.length || 0} tracks`,
      );

      playlist2Save = {
        ...playlist,
        name: detail.name,
        coverUrl: detail.image,
      };
      this.createPlaylistFolderStructure(playlist2Save.name);
    } catch (err) {
      this.logger.error(`Error getting playlist details: ${err}`);
      playlist2Save = {
        ...playlist,
        error: toSafeErrorMessage(err),
      };
    }
    const savedPlaylist = await this.save(playlist2Save);

    if (detail?.tracks && detail.tracks.length > 0) {
      this.logger.debug(
        `Starting to process ${detail.tracks.length} tracks for playlist ${savedPlaylist.name}`,
      );

      let processedCount = 0;
      let skippedCount = 0;
      let errorCount = 0;

      for (const track of detail.tracks) {
        try {
          if (!track.artist || !track.name) {
            this.logger.warn(
              `Skipping track ${processedCount + skippedCount + 1}: Missing artist or name information`,
            );
            skippedCount++;
            continue;
          }

          if (track.unavailable === true) {
            this.logger.warn(
              `Skipping unavailable track ${processedCount + skippedCount + 1}: ${track.artist} - ${track.name}`,
            );
            skippedCount++;
            continue;
          }

          await this.trackService.create(
            {
              artist: track.artist,
              name: track.name,
              spotifyUrl: track.previewUrl || null,
              coverUrl: track.coverUrl || savedPlaylist.coverUrl, // Use track's album art, fallback to playlist cover
              durationMs: track.durationMs,
            },
            savedPlaylist,
          );

          processedCount++;

          if (processedCount % 100 === 0) {
            this.logger.debug(
              `Processed ${processedCount} tracks so far for playlist ${savedPlaylist.name}`,
            );
          }
        } catch (error) {
          this.logger.error(
            `Error creating track "${
              track?.artist || 'Unknown'
            } - ${track?.name || 'Unknown'}": ${error.message}`,
          );
          errorCount++;
        }
      }

      this.logger.debug(
        `Finished processing playlist ${savedPlaylist.name}: ` +
          `${processedCount} tracks processed, ${skippedCount} skipped, ${errorCount} errors`,
      );
    } else {
      this.logger.warn(`No tracks found for playlist ${savedPlaylist.name}`);
    }
  }

  async save(playlist: PlaylistEntity): Promise<PlaylistEntity> {
    const savedPlaylist = await this.repository.save(playlist);
    this.io.emit(WsPlaylistOperation.New, savedPlaylist);
    return savedPlaylist;
  }

  async update(id: number, playlist: Partial<PlaylistEntity>): Promise<void> {
    await this.repository.update(id, playlist);
    const dbPlaylist = await this.findOne(id);
    this.io.emit(WsPlaylistOperation.Update, dbPlaylist);
  }

  async retryFailedOfPlaylist(id: number): Promise<void> {
    const tracks = await this.trackService.getAllByPlaylist(id);
    for (const track of tracks) {
      if (track.status === TrackStatusEnum.Error) {
        await this.trackService.retry(track.id);
      }
    }
  }

  private createPlaylistFolderStructure(playlistName: string): void {
    const playlistPath = this.utilsService.getPlaylistFolderPath(playlistName);
    !fs.existsSync(playlistPath) && fs.mkdirSync(playlistPath, { recursive: true });
  }

  @Interval(3_600_000)
  async checkActivePlaylists(): Promise<void> {
    // Only check actual playlists (not individual tracks) that are subscribed
    const activePlaylists = await this.findAll(
      { active: true, isTrack: false },
    );
    for (const playlist of activePlaylists) {
      let tracks = [];
      try {
        tracks = await this.spotifyService.getPlaylistTracks(
          playlist.spotifyUrl,
        );
        this.createPlaylistFolderStructure(playlist.name);
      } catch (err) {
        await this.update(playlist.id, {
          ...playlist,
          error: toSafeErrorMessage(err),
        });
      }
      for (const track of tracks ?? []) {
        const track2Save = {
          artist: track.artist,
          name: track.name,
          spotifyUrl: track.previewUrl,
        };
        const isExist = !!(
          await this.trackService.getAll({
            ...track2Save,
            playlist: { id: playlist.id },
          })
        ).length;
        if (!isExist) {
          await this.trackService.create(track2Save, playlist);
        }
      }
    }
  }
}
