//
// NotificationManager.swift
// IceGlass
//
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
//

import CoreGraphics
import Foundation
import ImageIO
import UniformTypeIdentifiers
import UserNotifications

class NotificationManager: @unchecked Sendable {
    private let logger = IceGlassLogger(
        subsystem: Bundle.main.bundleIdentifier ?? "dev.rzen.indie.IceGlass",
        category: "NotificationManager"
    )

    static let shared = NotificationManager()

    /// Track which game starts we've already notified about
    private var gameStartsSent = Set<Int>()

    /// Track which score changes we've already notified about (gameId-awayScore-homeScore)
    private var scoreChangesSent = Set<String>()

    /// Track which game endings we've already notified about
    private var gameEndsSent = Set<Int>()

    private init() {
        logger.info("Initializing")
        requestNotificationPermissions()
    }

    private func requestNotificationPermissions() {
        UNUserNotificationCenter.current().getNotificationSettings { [weak self] settings in
            guard let self = self else { return }

            self.logger.info("Current notification settings: \(settings.authorizationStatus.rawValue)")

            UNUserNotificationCenter.current().requestAuthorization(
                options: [.alert, .sound, .provisional, .badge]
            ) { [weak self] granted, error in
                guard let self = self else { return }

                if let error = error {
                    self.logger.error("Failed to request notification permission: \(error.localizedDescription)")
                }
                if granted {
                    self.logger.info("Notification permission granted")
                } else {
                    self.logger.warning("Notification permission denied")
                }
            }
        }
    }

    // MARK: - Game Started

    func notifyGameStarted(_ game: Scoreboard.Game, bypassDedup: Bool = false) {
        if !bypassDedup {
            guard !gameStartsSent.contains(game.id) else { return }
            gameStartsSent.insert(game.id)
        }

        let content = UNMutableNotificationContent()
        content.title = "Game Started"
        content.body = "\(game.awayTeam.abbrev) @ \(game.homeTeam.abbrev)"
        content.sound = .default
        content.interruptionLevel = .active
        if let url = game.gameCenterUrl {
            content.userInfo = ["url": url]
        }

        // Attach away team logo (visiting team at the other team's arena)
        if let attachment = teamLogoAttachment(for: game.awayTeam.abbrev) {
            content.attachments = [attachment]
        }

        let identifier = bypassDedup
            ? "game-start-test-\(UUID().uuidString)"
            : "game-start-\(game.id)"

        let request = UNNotificationRequest(
            identifier: identifier,
            content: content,
            trigger: UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
        )

        UNUserNotificationCenter.current().add(request) { [weak self] error in
            if let error = error {
                self?.logger.error("Error sending game start notification: \(error.localizedDescription)")
            } else {
                self?.logger.info("Game start notification sent for \(game.awayTeam.abbrev) @ \(game.homeTeam.abbrev)")
            }
        }
    }

    // MARK: - Goal Scored

    func notifyGoalScored(
        _ game: Scoreboard.Game,
        scoringTeam: Scoreboard.Game.Team,
        scorer: GoalScorer? = nil,
        bypassDedup: Bool = false
    ) {
        let awayScore = game.awayTeam.score ?? 0
        let homeScore = game.homeTeam.score ?? 0
        let key = "\(game.id)-\(awayScore)-\(homeScore)"

        if !bypassDedup {
            guard !scoreChangesSent.contains(key) else { return }
            scoreChangesSent.insert(key)
        }

        let content = UNMutableNotificationContent()
        content.title = "\(scoringTeam.abbrev) Goal!"
        if let scorer = scorer {
            content.subtitle = scorer.displayLine
        }
        content.body = "\(game.awayTeam.abbrev) \(awayScore) : \(game.homeTeam.abbrev) \(homeScore)"
        content.sound = UNNotificationSound(named: UNNotificationSoundName("Glass"))
        content.interruptionLevel = .active
        if let url = game.gameCenterUrl {
            content.userInfo = ["url": url]
        }

        // Attach scoring team's logo
        if let attachment = teamLogoAttachment(for: scoringTeam.abbrev) {
            content.attachments = [attachment]
        }

        let identifier = bypassDedup
            ? "goal-test-\(UUID().uuidString)"
            : "goal-\(key)"

        let request = UNNotificationRequest(
            identifier: identifier,
            content: content,
            trigger: UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
        )

        UNUserNotificationCenter.current().add(request) { [weak self] error in
            if let error = error {
                self?.logger.error("Error sending goal notification: \(error.localizedDescription)")
            } else {
                self?.logger.info("Goal notification sent: \(scoringTeam.abbrev) scored, \(awayScore):\(homeScore)")
            }
        }
    }

    // MARK: - Game Ended

    func notifyGameEnded(_ game: Scoreboard.Game, bypassDedup: Bool = false) {
        if !bypassDedup {
            guard !gameEndsSent.contains(game.id) else { return }
            gameEndsSent.insert(game.id)
        }

        let awayScore = game.awayTeam.score ?? 0
        let homeScore = game.homeTeam.score ?? 0
        let winner: Scoreboard.Game.Team
        let loser: Scoreboard.Game.Team
        if awayScore > homeScore {
            winner = game.awayTeam
            loser = game.homeTeam
        } else if homeScore > awayScore {
            winner = game.homeTeam
            loser = game.awayTeam
        } else {
            // Tie — shouldn't happen in NHL, but handle gracefully
            winner = game.homeTeam
            loser = game.awayTeam
        }
        let winnerScore = max(awayScore, homeScore)
        let loserScore = min(awayScore, homeScore)

        let content = UNMutableNotificationContent()
        content.title = "\(winner.abbrev) wins"
        content.body = "\(winner.abbrev) \(winnerScore) — \(loser.abbrev) \(loserScore)"
        content.sound = .default
        content.interruptionLevel = .active
        if let url = game.gameCenterUrl {
            content.userInfo = ["url": url]
        }

        // Attach winning team's logo
        if let attachment = teamLogoAttachment(for: winner.abbrev) {
            content.attachments = [attachment]
        }

        let identifier = bypassDedup
            ? "game-end-test-\(UUID().uuidString)"
            : "game-end-\(game.id)"

        let request = UNNotificationRequest(
            identifier: identifier,
            content: content,
            trigger: UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
        )

        UNUserNotificationCenter.current().add(request) { [weak self] error in
            if let error = error {
                self?.logger.error("Error sending game end notification: \(error.localizedDescription)")
            } else {
                self?.logger.info("Game end notification sent: \(winner.abbrev) \(winnerScore) — \(loser.abbrev) \(loserScore)")
            }
        }
    }

    // MARK: - Team Logo Attachment

    /// Creates a UNNotificationAttachment from the bundled team logo PNG.
    /// PNGs live on the filesystem (TeamLogos/ folder) rather than the asset
    /// catalog because UNNotificationAttachment takes a file URL — asset
    /// catalog images aren't accessible as standalone files.
    ///
    /// PNGs are pre-rendered at 48×48 (the measured native thumbnail size)
    /// so macOS doesn't downsample them during display.
    private func teamLogoAttachment(for teamAbbrev: String) -> UNNotificationAttachment? {
        guard let logoURL = Bundle.main.url(
            forResource: teamAbbrev, withExtension: "png", subdirectory: "TeamLogos"
        ) else {
            logger.debug("No logo found for \(teamAbbrev)")
            return nil
        }

        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("team-logos", isDirectory: true)
        try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
        let tempFile = tempDir.appendingPathComponent("\(teamAbbrev)-\(UUID().uuidString).png")

        do {
            try FileManager.default.copyItem(at: logoURL, to: tempFile)
            return try UNNotificationAttachment(
                identifier: "team-logo-\(teamAbbrev)",
                url: tempFile,
                options: [
                    UNNotificationAttachmentOptionsTypeHintKey: UTType.png.identifier,
                    UNNotificationAttachmentOptionsThumbnailHiddenKey: false
                ]
            )
        } catch {
            logger.error("Failed to create logo attachment for \(teamAbbrev): \(error.localizedDescription)")
            return nil
        }
    }

    func resetForNewDay() {
        logger.debug("Resetting notification tracking for new day")
        gameStartsSent.removeAll()
        scoreChangesSent.removeAll()
        gameEndsSent.removeAll()
    }

#if DEBUG
    // MARK: - Thumbnail Size Test

    /// Fires one notification per size in the ladder with a distinctive test
    /// image generated at that exact pixel size. The crispest thumbnail
    /// reveals the notification system's native display size.
    func sendThumbnailSizeTest() {
        let sizes = [16, 24, 32, 40, 48, 56, 64, 80, 96, 128]
        for size in sizes {
            guard let attachment = testAttachment(size: size) else {
                logger.warning("Failed to create test attachment for size \(size)")
                continue
            }

            let content = UNMutableNotificationContent()
            content.title = "Thumb Test"
            content.body = "\(size)px"
            content.sound = nil
            content.interruptionLevel = .active
            content.attachments = [attachment]

            let request = UNNotificationRequest(
                identifier: "thumb-test-\(size)-\(UUID().uuidString)",
                content: content,
                trigger: UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
            )
            UNUserNotificationCenter.current().add(request) { [weak self] error in
                if let error = error {
                    self?.logger.error("Thumb test \(size)px failed: \(error.localizedDescription)")
                }
            }
        }
    }

    /// Builds a UNNotificationAttachment with a generated NxN PNG.
    /// Pattern: solid white background, 1-source-pixel black border, a
    /// small centered 4x4 black pixel grid (so we can see pixel-level
    /// behaviour). Unique filename + attachment identifier so macOS
    /// treats each size as a distinct attachment (no cache reuse).
    private func testAttachment(size: Int) -> UNNotificationAttachment? {
        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent("thumb-test", isDirectory: true)
        try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
        let tempFile = tempDir.appendingPathComponent("thumb-\(size)-\(UUID().uuidString).png")

        guard generateTestPNG(size: size, at: tempFile) else { return nil }

        do {
            return try UNNotificationAttachment(
                identifier: "thumb-test-attachment-\(size)-\(UUID().uuidString)",
                url: tempFile,
                options: [
                    UNNotificationAttachmentOptionsTypeHintKey: UTType.png.identifier,
                    UNNotificationAttachmentOptionsThumbnailHiddenKey: false
                ]
            )
        } catch {
            logger.error("Failed to create thumb test attachment (\(size)): \(error.localizedDescription)")
            return nil
        }
    }

    private func generateTestPNG(size: Int, at url: URL) -> Bool {
        let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) ?? CGColorSpaceCreateDeviceRGB()
        guard let ctx = CGContext(
            data: nil, width: size, height: size,
            bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace,
            bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
        ) else { return false }

        // White fill
        ctx.setFillColor(red: 1, green: 1, blue: 1, alpha: 1)
        ctx.fill(CGRect(x: 0, y: 0, width: size, height: size))

        // 1-pixel black border (no antialiasing for pixel accuracy)
        ctx.setShouldAntialias(false)
        ctx.setFillColor(red: 0, green: 0, blue: 0, alpha: 1)
        // Top row
        ctx.fill(CGRect(x: 0, y: size - 1, width: size, height: 1))
        // Bottom row
        ctx.fill(CGRect(x: 0, y: 0, width: size, height: 1))
        // Left column
        ctx.fill(CGRect(x: 0, y: 0, width: 1, height: size))
        // Right column
        ctx.fill(CGRect(x: size - 1, y: 0, width: 1, height: size))

        // Centered 4x4 black square at exact source pixel positions
        let cx = size / 2
        let cy = size / 2
        ctx.fill(CGRect(x: cx - 2, y: cy - 2, width: 4, height: 4))

        guard let image = ctx.makeImage(),
              let dest = CGImageDestinationCreateWithURL(url as CFURL, UTType.png.identifier as CFString, 1, nil)
        else { return false }
        CGImageDestinationAddImage(dest, image, nil)
        return CGImageDestinationFinalize(dest)
    }
#endif
}
