use std::sync::Arc;
use uuid::Uuid;

use crate::application::dtos::playlist_dto::{
    AudioMetadataDto, CreatePlaylistDto, PlaylistDto, PlaylistItemDto, UpdatePlaylistDto,
};
use crate::application::ports::music_ports::MusicStoragePort;
use crate::common::errors::{DomainError, ErrorKind};
use crate::domain::entities::playlist::Playlist;
use crate::domain::repositories::playlist_repository::{
    AudioMetadataRepository, PlaylistItemRepository, PlaylistRepository,
};
use crate::infrastructure::repositories::pg::{
    AudioMetadataPgRepository, PlaylistItemPgRepository, PlaylistPgRepository,
};

pub struct MusicStorageAdapter {
    playlist_repository: Arc<PlaylistPgRepository>,
    item_repository: Arc<PlaylistItemPgRepository>,
    audio_metadata_repository: Arc<AudioMetadataPgRepository>,
}

impl MusicStorageAdapter {
    pub fn new(
        playlist_repository: Arc<PlaylistPgRepository>,
        item_repository: Arc<PlaylistItemPgRepository>,
        audio_metadata_repository: Arc<AudioMetadataPgRepository>,
    ) -> Self {
        Self {
            playlist_repository,
            item_repository,
            audio_metadata_repository,
        }
    }
}

impl MusicStoragePort for MusicStorageAdapter {
    async fn create_playlist(
        &self,
        dto: CreatePlaylistDto,
        user_id: Uuid,
    ) -> Result<PlaylistDto, DomainError> {
        let playlist = Playlist::new(dto.name, user_id, dto.description)?;
        let created = self.playlist_repository.create_playlist(playlist).await?;
        Ok(PlaylistDto::from(created))
    }

    async fn update_playlist(
        &self,
        playlist_id: &str,
        dto: UpdatePlaylistDto,
    ) -> Result<PlaylistDto, DomainError> {
        let uuid = Uuid::parse_str(playlist_id).map_err(|_| {
            DomainError::new(ErrorKind::InvalidInput, "Playlist", "Invalid playlist ID")
        })?;

        let mut playlist = self.playlist_repository.find_playlist_by_id(&uuid).await?;

        if let Some(name) = dto.name {
            playlist.update_name(name)?;
        }
        if let Some(description) = dto.description {
            playlist.update_description(Some(description));
        }
        if let Some(is_public) = dto.is_public {
            playlist.set_public(is_public);
        }
        if let Some(cover_file_id) = dto.cover_file_id {
            let cover_uuid = Uuid::parse_str(&cover_file_id).map_err(|_| {
                DomainError::new(ErrorKind::InvalidInput, "Playlist", "Invalid cover file ID")
            })?;
            playlist.set_cover(Some(cover_uuid));
        }

        let updated = self.playlist_repository.update_playlist(playlist).await?;
        Ok(PlaylistDto::from(updated))
    }

    async fn delete_playlist(&self, playlist_id: &str) -> Result<(), DomainError> {
        let uuid = Uuid::parse_str(playlist_id).map_err(|_| {
            DomainError::new(ErrorKind::InvalidInput, "Playlist", "Invalid playlist ID")
        })?;
        self.playlist_repository.delete_playlist(&uuid).await
    }

    async fn get_playlist(&self, playlist_id: &str) -> Result<Option<PlaylistDto>, DomainError> {
        let uuid = Uuid::parse_str(playlist_id).map_err(|_| {
            DomainError::new(ErrorKind::InvalidInput, "Playlist", "Invalid playlist ID")
        })?;

        match self.playlist_repository.find_playlist_by_id(&uuid).await {
            Ok(playlist) => Ok(Some(PlaylistDto::from(playlist))),
            Err(e) if e.kind == ErrorKind::NotFound => Ok(None),
            Err(e) => Err(e),
        }
    }

    async fn list_playlists_by_owner(
        &self,
        owner_id: Uuid,
    ) -> Result<Vec<PlaylistDto>, DomainError> {
        let playlists = self
            .playlist_repository
            .list_playlists_by_owner(owner_id)
            .await?;
        let mut result = Vec::new();
        for playlist in playlists {
            let dto = PlaylistDto::from(playlist);
            let track_count = self
                .get_track_count(&uuid::Uuid::parse_str(&dto.id).unwrap())
                .await?;
            result.push(dto.with_track_info(track_count, 0));
        }
        Ok(result)
    }

    async fn list_shared_with_user(&self, user_id: Uuid) -> Result<Vec<PlaylistDto>, DomainError> {
        let playlists = self
            .playlist_repository
            .list_shared_with_user(user_id)
            .await?;
        let mut result = Vec::new();
        for playlist in playlists {
            let dto = PlaylistDto::from(playlist);
            let track_count = self
                .get_track_count(&uuid::Uuid::parse_str(&dto.id).unwrap())
                .await?;
            result.push(dto.with_track_info(track_count, 0));
        }
        Ok(result)
    }

    async fn list_public_playlists(
        &self,
        limit: i64,
        offset: i64,
    ) -> Result<Vec<PlaylistDto>, DomainError> {
        let playlists = self
            .playlist_repository
            .list_public_playlists(limit, offset)
            .await?;
        let mut result = Vec::new();
        for playlist in playlists {
            let dto = PlaylistDto::from(playlist);
            let track_count = self
                .get_track_count(&uuid::Uuid::parse_str(&dto.id).unwrap())
                .await?;
            result.push(dto.with_track_info(track_count, 0));
        }
        Ok(result)
    }

    async fn user_has_access(&self, playlist_id: &str, user_id: Uuid) -> Result<bool, DomainError> {
        let uuid = Uuid::parse_str(playlist_id).map_err(|_| {
            DomainError::new(ErrorKind::InvalidInput, "Playlist", "Invalid playlist ID")
        })?;
        self.playlist_repository
            .user_has_access(&uuid, user_id)
            .await
    }

    async fn user_can_write(&self, playlist_id: &str, user_id: Uuid) -> Result<bool, DomainError> {
        let uuid = Uuid::parse_str(playlist_id).map_err(|_| {
            DomainError::new(ErrorKind::InvalidInput, "Playlist", "Invalid playlist ID")
        })?;

        let playlist = self.playlist_repository.find_playlist_by_id(&uuid).await?;
        if playlist.owner_id() == &user_id {
            return Ok(true);
        }

        let shares = self.playlist_repository.get_shares(&uuid).await?;
        Ok(shares
            .iter()
            .any(|(uid, can_write)| uid == &user_id && *can_write))
    }

    async fn add_tracks(
        &self,
        playlist_id: &Uuid,
        file_ids: &[Uuid],
    ) -> Result<Vec<PlaylistItemDto>, DomainError> {
        let mut max_position = self.item_repository.get_max_position(playlist_id).await?;
        let mut items = Vec::new();

        for file_id in file_ids {
            max_position += 1;
            let item = crate::domain::entities::playlist::PlaylistItem::new(
                *playlist_id,
                *file_id,
                max_position,
            )?;
            let created = self.item_repository.add_item(item).await?;
            items.push(PlaylistItemDto::from(created));
        }

        Ok(items)
    }

    async fn remove_track(&self, playlist_id: &Uuid, file_id: &Uuid) -> Result<(), DomainError> {
        self.item_repository
            .remove_item_by_playlist_and_file(playlist_id, file_id)
            .await
    }

    async fn reorder_tracks(
        &self,
        playlist_id: &Uuid,
        item_ids: &[Uuid],
    ) -> Result<(), DomainError> {
        self.item_repository
            .reorder_items(playlist_id, item_ids)
            .await
    }

    async fn list_playlist_tracks(
        &self,
        playlist_id: &Uuid,
    ) -> Result<Vec<PlaylistItemDto>, DomainError> {
        let enriched_items = self
            .item_repository
            .list_items_in_playlist_enriched(playlist_id)
            .await?;

        let items: Vec<PlaylistItemDto> = enriched_items
            .into_iter()
            .map(
                |row| crate::application::dtos::playlist_dto::PlaylistItemDto {
                    id: row.id.to_string(),
                    playlist_id: row.playlist_id.to_string(),
                    file_id: row.file_id.to_string(),
                    position: row.position,
                    added_at: row.added_at,
                    file_name: row.file_name,
                    file_size: row.file_size,
                    mime_type: row.mime_type,
                    title: row.title,
                    artist: row.artist,
                    album: row.album,
                    duration_secs: row.duration_secs,
                },
            )
            .collect();
        Ok(items)
    }

    async fn share_playlist(
        &self,
        playlist_id: &Uuid,
        user_id: Uuid,
        can_write: bool,
    ) -> Result<(), DomainError> {
        self.playlist_repository
            .share_playlist(playlist_id, user_id, can_write)
            .await
    }

    async fn remove_share(&self, playlist_id: &Uuid, user_id: Uuid) -> Result<(), DomainError> {
        self.playlist_repository
            .remove_share(playlist_id, user_id)
            .await
    }

    async fn get_shares(&self, playlist_id: &Uuid) -> Result<Vec<(Uuid, bool)>, DomainError> {
        self.playlist_repository.get_shares(playlist_id).await
    }

    async fn get_audio_metadata(
        &self,
        file_id: &Uuid,
    ) -> Result<Option<AudioMetadataDto>, DomainError> {
        match self
            .audio_metadata_repository
            .find_by_file_id(file_id)
            .await
        {
            Ok(Some(metadata)) => Ok(Some(AudioMetadataDto::from(metadata))),
            Ok(None) => Ok(None),
            Err(e) => Err(e),
        }
    }
}

impl MusicStorageAdapter {
    async fn get_track_count(&self, playlist_id: &Uuid) -> Result<i64, DomainError> {
        let count: (i64,) =
            sqlx::query_as("SELECT COUNT(*) FROM audio.playlist_items WHERE playlist_id = $1")
                .bind(playlist_id)
                .fetch_one(self.playlist_repository.pool())
                .await
                .map_err(|e| {
                    DomainError::database_error(format!("Failed to get track count: {}", e))
                })?;
        Ok(count.0)
    }
}
