import Foundation
import Observation
import GRDB

@Observable
final class PlaylistViewModel {
    var playlists: [Playlist] = []
    var smartPlaylists: [SmartPlaylist] = []
    var selectedItem: (any PlaylistRepresentable)?
    var playlistTracks: [Track] = []

    var allPlaylists: [any PlaylistRepresentable] {
        let regular: [any PlaylistRepresentable] = playlists
        let smart: [any PlaylistRepresentable] = smartPlaylists
        return (regular + smart).sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
    }

    var selectedPlaylist: Playlist? {
        selectedItem as? Playlist
    }

    var selectedSmartPlaylist: SmartPlaylist? {
        selectedItem as? SmartPlaylist
    }

    var lastUsedPlaylistId: Int64? {
        get { UserDefaults.standard.object(forKey: "lastUsedPlaylistId") as? Int64 }
        set { UserDefaults.standard.set(newValue, forKey: "lastUsedPlaylistId") }
    }

    var lastUsedPlaylistName: String? {
        guard let id = lastUsedPlaylistId else { return nil }
        return playlists.first(where: { $0.id == id })?.name
    }

    private let db: DatabaseService
    private var playlistsCancellable: AnyDatabaseCancellable?
    private var smartPlaylistsCancellable: AnyDatabaseCancellable?
    private var tracksCancellable: AnyDatabaseCancellable?
    private var searchTask: Task<Void, Never>?
    private var searchText = ""
    var sortColumn = "title"
    var sortAscending = true

    init(db: DatabaseService) {
        self.db = db
        observePlaylists()
        observeSmartPlaylists()
    }

    // MARK: - Regular Playlists

    func createPlaylist(name: String) throws {
        _ = try db.createPlaylist(name: name)
    }

    func renamePlaylist(_ playlist: Playlist, to name: String) throws {
        guard let id = playlist.id else { return }
        try db.renamePlaylist(id: id, name: name)
    }

    func deletePlaylist(_ playlist: Playlist) throws {
        guard let id = playlist.id else { return }
        if selectedPlaylist?.id == id {
            deselectPlaylist()
        }
        if lastUsedPlaylistId == id {
            lastUsedPlaylistId = nil
        }
        try db.deletePlaylist(id: id)
    }

    func addTrack(_ track: Track, to playlist: Playlist) throws {
        guard let trackId = track.id, let playlistId = playlist.id else { return }
        try db.addTrackToPlaylist(trackId: trackId, playlistId: playlistId)
        lastUsedPlaylistId = playlistId
    }

    func addTrackToLastUsedPlaylist(_ track: Track) throws {
        guard let playlistId = lastUsedPlaylistId,
              let playlist = playlists.first(where: { $0.id == playlistId }) else { return }
        try addTrack(track, to: playlist)
    }

    func removeTrack(_ track: Track, from playlist: Playlist) throws {
        guard let trackId = track.id, let playlistId = playlist.id else { return }
        try db.removeTrackFromPlaylist(trackId: trackId, playlistId: playlistId)
    }

    func moveTrack(in playlist: Playlist, from source: Int, to destination: Int) throws {
        guard let playlistId = playlist.id else { return }
        try db.reorderPlaylistTrack(playlistId: playlistId, fromPosition: source, toPosition: destination)
    }

    // MARK: - Smart Playlists

    func createSmartPlaylist(searchQuery: String) throws {
        let name = searchQuery
            .split(separator: " ")
            .map { $0.prefix(1).uppercased() + $0.dropFirst().lowercased() }
            .joined(separator: " ")
        _ = try db.createSmartPlaylist(name: name, searchQuery: searchQuery)
    }

    func renameSmartPlaylist(_ smartPlaylist: SmartPlaylist, to name: String) throws {
        guard let id = smartPlaylist.id else { return }
        try db.renameSmartPlaylist(id: id, name: name)
    }

    func updateSmartPlaylistQuery(_ smartPlaylist: SmartPlaylist, to query: String) throws {
        guard let id = smartPlaylist.id else { return }
        try db.updateSmartPlaylistQuery(id: id, searchQuery: query)
        if selectedSmartPlaylist?.id == id {
            observeSmartPlaylistTracks(searchQuery: query)
        }
    }

    func deleteSmartPlaylist(_ smartPlaylist: SmartPlaylist) throws {
        guard let id = smartPlaylist.id else { return }
        if selectedSmartPlaylist?.id == id {
            deselectPlaylist()
        }
        try db.deleteSmartPlaylist(id: id)
    }

    // MARK: - Selection

    func selectItem(_ item: any PlaylistRepresentable) {
        selectedItem = item
        if item is Playlist {
            observePlaylistTracks()
        } else if let smart = item as? SmartPlaylist {
            observeSmartPlaylistTracks(searchQuery: smart.searchQuery)
        }
    }

    func deselectPlaylist() {
        selectedItem = nil
        tracksCancellable?.cancel()
        tracksCancellable = nil
        playlistTracks = []
        searchText = ""
    }

    func sort(by column: String) {
        if sortColumn == column {
            sortAscending.toggle()
        } else {
            sortColumn = column
            sortAscending = true
        }
        if let smart = selectedSmartPlaylist {
            observeSmartPlaylistTracks(searchQuery: smart.searchQuery)
        }
    }

    func search(_ text: String) {
        searchText = text
        searchTask?.cancel()
        searchTask = Task { @MainActor [weak self] in
            try? await Task.sleep(for: .milliseconds(150))
            guard !Task.isCancelled else { return }
            if self?.selectedPlaylist != nil {
                self?.observePlaylistTracks()
            }
        }
    }

    // MARK: - Observation

    private func observePlaylists() {
        let observation = ValueObservation.tracking { [db] dbAccess in
            try db.fetchPlaylists(db: dbAccess)
        }
        playlistsCancellable = observation.start(
            in: db.dbPool,
            onError: { error in print("Playlists observation error: \(error)") },
            onChange: { [weak self] playlists in
                self?.playlists = playlists
                if let selectedId = self?.selectedPlaylist?.id {
                    self?.selectedItem = playlists.first(where: { $0.id == selectedId })
                }
            }
        )
    }

    private func observeSmartPlaylists() {
        let observation = ValueObservation.tracking { [db] dbAccess in
            try db.fetchSmartPlaylists(db: dbAccess)
        }
        smartPlaylistsCancellable = observation.start(
            in: db.dbPool,
            onError: { error in print("Smart playlists observation error: \(error)") },
            onChange: { [weak self] smartPlaylists in
                self?.smartPlaylists = smartPlaylists
                if let selectedId = self?.selectedSmartPlaylist?.id {
                    self?.selectedItem = smartPlaylists.first(where: { $0.id == selectedId })
                }
            }
        )
    }

    private func observePlaylistTracks() {
        tracksCancellable?.cancel()
        guard let playlistId = selectedPlaylist?.id else { return }
        let search = searchText

        let observation = ValueObservation.tracking { [db] dbAccess in
            try db.fetchPlaylistTracks(db: dbAccess, playlistId: playlistId, search: search)
        }
        tracksCancellable = observation.start(
            in: db.dbPool,
            onError: { error in print("Playlist tracks observation error: \(error)") },
            onChange: { [weak self] tracks in self?.playlistTracks = tracks }
        )
    }

    private func observeSmartPlaylistTracks(searchQuery: String) {
        tracksCancellable?.cancel()
        let col = sortColumn
        let asc = sortAscending

        let observation = ValueObservation.tracking { [db] dbAccess in
            try db.fetchTracks(db: dbAccess, search: searchQuery, sortColumn: col, ascending: asc)
        }
        tracksCancellable = observation.start(
            in: db.dbPool,
            onError: { error in print("Smart playlist tracks observation error: \(error)") },
            onChange: { [weak self] tracks in self?.playlistTracks = tracks }
        )
    }
}
