import Foundation
import Observation

protocol RemoteCommandSender: AnyObject {
    func sendCommand(_ command: RemoteCommand)
}

@Observable
final class PlayerViewModel {
    var currentTrack: Track?
    var currentIndex: Int?
    var isShuffled = false
    var isPlaying = false
    var currentTime: Double = 0
    var duration: Double = 0
    var volume: Float = 0.65

    private(set) var queue: [Track] = []
    private var originalQueue: [Track] = []
    private let audio: AudioService
    private let db: DatabaseService?
    private var halfwayReported = false

    private var remoteClient: RemoteCommandSender?
    var trackResolver: ((Int64) -> Track?)?

    private var isRemote: Bool { remoteClient != nil }

    init(audio: AudioService, db: DatabaseService?) {
        self.audio = audio
        self.db = db

        audio.onTrackFinished = { [weak self] in
            self?.trackDidFinish()
        }

        audio.onPlaybackStateChanged = { [weak self] in
            self?.syncFromAudio()
        }
    }

    // MARK: - Audio Sync

    private func syncFromAudio() {
        guard !isRemote else { return }
        isPlaying = audio.isPlaying
        if !audio.isScrubbing {
            currentTime = audio.currentTime
        }
        duration = audio.duration
        checkHalfway()
    }

    // MARK: - Queue Management

    func setQueue(_ tracks: [Track]) {
        originalQueue = tracks
        if isShuffled {
            queue = buildShuffledQueue(from: tracks, startingWith: currentTrack)
        } else {
            queue = tracks
        }
        if let current = currentTrack {
            currentIndex = queue.firstIndex(where: { $0.id == current.id })
        }
    }

    // MARK: - Playback Controls

    func play(_ track: Track) {
        currentTrack = track
        currentIndex = queue.firstIndex(where: { $0.id == track.id })
        halfwayReported = false
        isPlaying = true
        currentTime = 0

        if let client = remoteClient {
            guard let trackId = track.id else { return }
            client.sendCommand(.play(trackId: trackId, queueIds: queue.compactMap(\.id)))
        } else {
            guard let url = URL(string: track.fileURL) else { return }
            audio.play(url: url)
        }
    }

    func togglePlayPause() {
        if isPlaying { pause() } else { resume() }
    }

    func pause() {
        isPlaying = false
        if let client = remoteClient { client.sendCommand(.pause) } else { audio.pause() }
    }

    func resume() {
        isPlaying = true
        if let client = remoteClient { client.sendCommand(.resume) } else { audio.resume() }
    }

    func seek(to position: Double) {
        currentTime = position
        if let client = remoteClient { client.sendCommand(.seek(position: position)) } else { audio.seek(to: position) }
    }

    func setVolume(_ level: Float) {
        volume = level
        if let client = remoteClient { client.sendCommand(.setVolume(level: level)) } else { audio.volume = level }
    }

    func beginScrubbing() {
        if !isRemote { audio.beginScrubbing() }
    }

    func scrub(to position: Double) {
        currentTime = position
        if !isRemote { audio.scrub(to: position) }
    }

    func endScrubbing(at position: Double) {
        currentTime = position
        if let client = remoteClient { client.sendCommand(.seek(position: position)) } else { audio.endScrubbing(at: position) }
    }

    func next() {
        if let client = remoteClient {
            client.sendCommand(.next)
            return
        }
        guard let idx = currentIndex else { return }
        let nextIdx = idx + 1
        if nextIdx < queue.count {
            play(queue[nextIdx])
        } else {
            stop()
        }
    }

    func previous() {
        if let client = remoteClient {
            client.sendCommand(.previous)
            return
        }
        guard let idx = currentIndex else { return }
        let prevIdx = max(0, idx - 1)
        play(queue[prevIdx])
    }

    func toggleShuffle() {
        isShuffled.toggle()
        if let client = remoteClient {
            client.sendCommand(.toggleShuffle)
            return
        }
        if isShuffled {
            queue = buildShuffledQueue(from: originalQueue, startingWith: currentTrack)
        } else {
            queue = originalQueue
        }
        if let current = currentTrack {
            currentIndex = queue.firstIndex(where: { $0.id == current.id })
        }
    }

    func stop() {
        isPlaying = false
        currentTime = 0
        duration = 0
        currentTrack = nil
        currentIndex = nil
        if !isRemote { audio.stop() }
    }

    // MARK: - Remote Mode

    func enterRemoteMode(client: RemoteCommandSender) {
        audio.stop()
        remoteClient = client
        currentTrack = nil
        currentIndex = nil
        isPlaying = false
        currentTime = 0
        duration = 0
        queue = []
        originalQueue = []
    }

    func exitRemoteMode() {
        remoteClient = nil
        trackResolver = nil
        currentTrack = nil
        currentIndex = nil
        isPlaying = false
        currentTime = 0
        duration = 0
        queue = []
        originalQueue = []
    }

    func applyRemoteState(_ state: PlaybackStatePayload) {
        guard isRemote else { return }
        isPlaying = state.isPlaying
        currentTime = state.currentTime
        duration = state.duration
        volume = state.volume
        isShuffled = state.isShuffled

        if let trackId = state.trackId, currentTrack?.id != trackId {
            currentTrack = trackResolver?(trackId)
        } else if state.trackId == nil {
            currentTrack = nil
        }
    }

    // MARK: - Internal

    func checkHalfway() {
        guard !halfwayReported,
              duration > 0,
              currentTime >= duration * 0.5,
              let track = currentTrack,
              let trackId = track.id else { return }

        halfwayReported = true
        let newCount = track.playCount + 1
        try? db?.updatePlayStats(trackId: trackId, playCount: newCount, lastPlayedAt: Date())
    }

    private func trackDidFinish() {
        guard !isRemote else { return }
        if let track = currentTrack, let trackId = track.id, !halfwayReported {
            let newCount = track.playCount + 1
            try? db?.updatePlayStats(trackId: trackId, playCount: newCount, lastPlayedAt: Date())
        }
        next()
    }

    private func buildShuffledQueue(from tracks: [Track], startingWith current: Track?) -> [Track] {
        var shuffled = tracks.shuffled()
        if let current, let idx = shuffled.firstIndex(where: { $0.id == current.id }) {
            shuffled.remove(at: idx)
            shuffled.insert(current, at: 0)
        }
        return shuffled
    }
}
