import AppKit
import CryptoKit
import Foundation
import os
import Observation

@Observable
@MainActor
final class NowPlayingArtworkResolver {
    private let logger = Logger(subsystem: "com.spalencsar.BearWave", category: "Artwork")
    private let cacheDirectory: URL
    private var memoryCache: [String: NSImage] = [:]
    private var inFlight: Set<String> = []
    private let faviconLoader: StationArtworkLoader
    var onArtworkLoaded: (() -> Void)?

    init(cacheDirectory: URL, faviconLoader: StationArtworkLoader) {
        self.cacheDirectory = cacheDirectory.appendingPathComponent("now_playing_artwork")
        self.faviconLoader = faviconLoader
    }

    func resolve(artist: String?, title: String?, station: RadioStation?) -> NSImage {
        let cacheKey = artworkCacheKey(artist: artist, title: title, station: station)

        if let cached = memoryCache[cacheKey] {
            return cached
        }

        if let diskImage = NSImage(contentsOf: diskCacheURL(for: cacheKey)) {
            memoryCache[cacheKey] = diskImage
            return diskImage
        }

        if let station {
            let favicon = faviconLoader.image(for: station) ?? BearWaveAssets.image(named: "bearwave") ?? NSImage()
            memoryCache[cacheKey] = favicon
            return favicon
        }

        return BearWaveAssets.image(named: "bearwave") ?? NSImage()
    }

    func fetchArtwork(artist: String?, title: String?, station: RadioStation?) {
        logger.info("fetchArtwork called: artist='\(artist ?? "nil")', title='\(title ?? "nil")'")
        guard let artist, let title, !artist.isEmpty, !title.isEmpty else {
            logger.info("guard failed: empty artist or title")
            return
        }

        let cacheKey = artworkCacheKey(artist: artist, title: title, station: station)
        guard !inFlight.contains(cacheKey) else {
            logger.info("already in flight for key: \(cacheKey)")
            return
        }
        inFlight.insert(cacheKey)

        Task {
            defer {
                Task { @MainActor in self.inFlight.remove(cacheKey) }
            }

            let query = "\(artist) \(title)".trimmingCharacters(in: .whitespacesAndNewlines)
            logger.info("iTunes search query: '\(query)'")
            let searchURL = Self.buildITunesSearchURL(query: query)
            guard let searchURL else {
                logger.info("failed to build search URL")
                return
            }

            do {
                var request = URLRequest(url: searchURL)
                request.timeoutInterval = 8
                request.setValue("BearWave-macOS/1.0", forHTTPHeaderField: "User-Agent")
                let (data, response) = try await URLSession.shared.data(for: request)
                guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
                    logger.info("search request failed: \(response)")
                    return
                }

                let decoder = JSONDecoder()
                let itunesResponse = try decoder.decode(ITunesResponse.self, from: data)
                logger.info("iTunes resultCount: \(itunesResponse.results.count)")
                guard let result = itunesResponse.results.first,
                      var artworkURL = URL(string: result.artworkUrl100) else {
                    logger.info("no artwork URL in response")
                    return
                }

                logger.info("artwork URL: \(artworkURL.absoluteString)")
                artworkURL = Self.highResArtworkURL(artworkURL)

                var imageRequest = URLRequest(url: artworkURL)
                imageRequest.timeoutInterval = 8
                let (imageData, imageResponse) = try await URLSession.shared.data(for: imageRequest)
                guard let httpImage = imageResponse as? HTTPURLResponse,
                      (200..<300).contains(httpImage.statusCode) else {
                    logger.info("image download failed: \(imageResponse)")
                    return
                }

                guard let image = NSImage(data: imageData) else {
                    logger.info("NSImage(data:) failed, data size: \(imageData.count)")
                    return
                }

                logger.info("image loaded, size: \(String(describing: image.size))")
                try? FileManager.default.createDirectory(at: cacheDirectory, withIntermediateDirectories: true)
                try? imageData.write(to: diskCacheURL(for: cacheKey), options: .atomic)

                await MainActor.run {
                    memoryCache[cacheKey] = image
                    logger.info("artwork cached and callback triggered")
                    self.onArtworkLoaded?()
                }
            } catch {
                logger.info("error: \(error)")
                return
            }
        }
    }

    private func artworkCacheKey(artist: String?, title: String?, station: RadioStation?) -> String {
        let artistPart = artist?.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        let titlePart = title?.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        let key = "\(artistPart)|\(titlePart)"
        logger.info("cacheKey: '\(key)'")
        return key
    }

    private func diskCacheURL(for key: String) -> URL {
        let digest = SHA256.hash(data: Data(key.utf8))
        let hash = digest.map { String(format: "%02x", $0) }.joined()
        return cacheDirectory.appendingPathComponent("\(hash).image")
    }

    private static func buildITunesSearchURL(query: String) -> URL? {
        var components = URLComponents(string: "https://itunes.apple.com/search")
        components?.queryItems = [
            URLQueryItem(name: "term", value: query),
            URLQueryItem(name: "media", value: "music"),
            URLQueryItem(name: "limit", value: "1")
        ]
        return components?.url
    }

    private static func highResArtworkURL(_ url: URL) -> URL {
        var urlString = url.absoluteString
        urlString = urlString.replacingOccurrences(of: "100x100", with: "600x600")
        return URL(string: urlString) ?? url
    }
}

private struct ITunesResponse: Codable {
    struct Result: Codable {
        let artworkUrl100: String
    }
    let results: [Result]
}
