import SwiftUI

@main
struct MusicApp: App {
    @State private var dbService: DatabaseService?
    @State private var libraryVM: LibraryViewModel?
    @State private var playerVM: PlayerViewModel?
    @State private var scannerService: ScannerService?
    @State private var audioService = AudioService()
    @State private var shazamService = ShazamService()
    @State private var playlistVM: PlaylistViewModel?
    @State private var showNewPlaylistAlert = false
    @State private var initError: String?
    @State private var hostServer: HostServer?
    @State private var remoteClient = RemoteClient()
    @State private var showConnectionSheet = false

    var body: some Scene {
        WindowGroup {
            Group {
                if let db = dbService,
                   let library = libraryVM,
                   let player = playerVM,
                   let scanner = scannerService,
                   let playlist = playlistVM {
                    ContentView(
                        library: library,
                        player: player,
                        scanner: scanner,
                        playlist: playlist,
                        shazam: shazamService,
                        db: db,
                        showNewPlaylistAlert: $showNewPlaylistAlert,
                        networkStatus: computeNetworkStatus()
                    )
                } else if let error = initError {
                    Text("Failed to initialize database: \(error)")
                        .padding()
                } else {
                    ProgressView("Loading...")
                        .onAppear { initialize() }
                }
            }
            .frame(minWidth: 800, minHeight: 500)
            .onChange(of: remoteClient.connectionState) { _, newState in
                if case .connected = newState {
                    enterRemoteMode()
                } else if newState == .disconnected {
                    exitRemoteMode()
                }
            }
            .sheet(isPresented: $showConnectionSheet) {
                ConnectionSheet(remoteClient: remoteClient, isPresented: $showConnectionSheet)
            }
        }
        .commands {
            CommandGroup(after: .newItem) {
                Button("Open Music Folder...") {
                    pickFolder()
                }
                .keyboardShortcut("o")
                .disabled(remoteClient.connectionState.isConnected)

                Button("New Playlist...") {
                    showNewPlaylistAlert = true
                }
                .keyboardShortcut("n")
                .disabled(remoteClient.connectionState.isConnected)

                Divider()

                Toggle("Enable Host Mode", isOn: Binding(
                    get: { hostServer?.isHosting ?? false },
                    set: { $0 ? startHosting() : hostServer?.stop() }
                ))
                .disabled(remoteClient.connectionState.isConnected)

                Button("Connect to Remote...") {
                    showConnectionSheet = true
                    remoteClient.startDiscovery()
                }
                .disabled(hostServer?.isHosting ?? false)
            }
        }
    }

    private func initialize() {
        do {
            let appSupport = FileManager.default.urls(
                for: .applicationSupportDirectory, in: .userDomainMask
            ).first!.appendingPathComponent("Music", isDirectory: true)
            try FileManager.default.createDirectory(at: appSupport, withIntermediateDirectories: true)

            let dbPath = appSupport.appendingPathComponent("db.sqlite").path
            let db = try DatabaseService(path: dbPath)
            let scanner = ScannerService(db: db)
            let library = LibraryViewModel(db: db)
            let player = PlayerViewModel(audio: audioService, db: db)
            let playlist = PlaylistViewModel(db: db)

            self.dbService = db
            self.scannerService = scanner
            self.libraryVM = library
            self.playerVM = player
            self.playlistVM = playlist

            if let url = resolveBookmark() {
                Task {
                    await scanner.rescan(url)
                }
            } else if !isRunningTests {
                DispatchQueue.main.async {
                    pickFolder()
                }
            }
        } catch {
            initError = error.localizedDescription
        }
    }

    private var isRunningTests: Bool {
        ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
    }

    private func pickFolder() {
        let panel = NSOpenPanel()
        panel.canChooseFiles = false
        panel.canChooseDirectories = true
        panel.allowsMultipleSelection = false
        panel.message = "Select your music folder"

        guard panel.runModal() == .OK, let url = panel.url else { return }

        saveBookmark(for: url)

        if let scanner = scannerService {
            Task {
                await scanner.scanFolder(url)
            }
        }
    }

    private func saveBookmark(for url: URL) {
        do {
            let data = try url.bookmarkData(
                options: .withSecurityScope,
                includingResourceValuesForKeys: nil,
                relativeTo: nil
            )
            UserDefaults.standard.set(data, forKey: "musicFolderBookmark")
        } catch {
            print("Failed to save bookmark: \(error)")
        }
    }

    private func resolveBookmark() -> URL? {
        guard let data = UserDefaults.standard.data(forKey: "musicFolderBookmark") else {
            return nil
        }
        do {
            var isStale = false
            let url = try URL(
                resolvingBookmarkData: data,
                options: .withSecurityScope,
                relativeTo: nil,
                bookmarkDataIsStale: &isStale
            )
            guard url.startAccessingSecurityScopedResource() else { return nil }
            if isStale {
                saveBookmark(for: url)
            }
            return url
        } catch {
            return nil
        }
    }

    // MARK: - Remote / Host

    private func startHosting() {
        guard let db = dbService, let player = playerVM else { return }
        let appSupport = FileManager.default.urls(
            for: .applicationSupportDirectory, in: .userDomainMask
        ).first!.appendingPathComponent("Music", isDirectory: true)
        let dbPath = appSupport.appendingPathComponent("db.sqlite").path

        let server = HostServer(dbPath: dbPath)
        server.configure(player: player, db: db)
        do {
            try server.start()
            hostServer = server
        } catch {
            print("Failed to start host: \(error)")
        }
    }

    private func enterRemoteMode() {
        guard let player = playerVM else { return }
        do {
            let remoteDb = try DatabaseService(path: RemoteClient.remoteDBPath)
            self.libraryVM = LibraryViewModel(db: remoteDb)
            self.playlistVM = PlaylistViewModel(db: remoteDb)

            player.enterRemoteMode(client: remoteClient)
            player.trackResolver = { trackId in
                self.libraryVM?.tracks.first(where: { $0.id == trackId })
            }

            remoteClient.onPlaybackState = { [weak player] state in
                player?.applyRemoteState(state)
            }
        } catch {
            print("Failed to load remote DB: \(error)")
            remoteClient.disconnect()
        }
    }

    private func exitRemoteMode() {
        playerVM?.exitRemoteMode()
        remoteClient.onPlaybackState = nil
        guard let db = dbService else { return }
        self.libraryVM = LibraryViewModel(db: db)
        self.playlistVM = PlaylistViewModel(db: db)
        try? FileManager.default.removeItem(atPath: RemoteClient.remoteDBPath)
    }

    private func computeNetworkStatus() -> NetworkStatus? {
        if remoteClient.connectionState.isConnected {
            let hostName: String
            if case .connected(let name) = remoteClient.connectionState { hostName = name } else { hostName = "Unknown" }
            return NetworkStatus(
                mode: .remote(hostName: hostName),
                onDisconnect: { [remoteClient] in remoteClient.disconnect() },
                onRefreshLibrary: { [remoteClient] in remoteClient.sendCommand(.refreshDB) }
            )
        }
        if let server = hostServer, server.isHosting {
            return NetworkStatus(mode: .hosting(connectedRemote: server.connectedRemoteName))
        }
        return nil
    }
}
