import SwiftUI
import XCTest
@testable import LocalSkyRadar

@MainActor
final class RadarFaceRenderTests: XCTestCase {
    func testAircraftMarkerRendersAtProjectedPosition() throws {
        let center = Airport(icao: "HERE", name: "Test Center", lat: 37.0, lon: -122.0)
        let aircraft = Aircraft(
            id: "test1",
            callsign: "TEST1",
            type: "B738",
            registration: "N001",
            lat: 37.5,
            lon: -122.0,
            altitudeFt: 10_000,
            groundSpeedKt: 250,
            trackDeg: 180,
            trend: .level,
            source: .adsb,
            distNM: 30,
            bearingDeg: 0
        )

        let view = RadarFace(
            center: center,
            aircraft: [aircraft],
            rangeNM: 100,
            scopeShape: .circle,
            theme: .muted
        )
        .frame(width: 400, height: 400)

        let image = try XCTUnwrap(ImageRenderer(content: view).cgImage)
        let expected = CoordinateMath.projectToScope(
            ac: aircraft,
            center: (lat: center.lat, lon: center.lon),
            rangeNM: 100,
            cx: 200,
            cy: 200,
            R: 178
        )

        XCTAssertTrue(
            image.containsAircraftPixels(nearX: Int(expected.x.rounded()), y: Int(expected.y.rounded())),
            "Expected an aircraft marker near projected coordinate \(expected), but no aircraft-colored pixels were rendered there."
        )
    }

    func testSubTwentyFiveNmRangeRendersOuterRingInsideScope() throws {
        let center = Airport(icao: "HERE", name: "Test Center", lat: 37.0, lon: -122.0)
        let view = RadarFace(
            center: center,
            aircraft: [],
            rangeNM: 15,
            scopeShape: .circle,
            theme: .muted
        )
        .frame(width: 400, height: 400)

        let image = try XCTUnwrap(ImageRenderer(content: view).cgImage)

        XCTAssertTrue(
            image.containsRingPixels(nearX: 378, y: 200, radius: 24),
            "Expected the 15 NM outer range ring to be drawn inside the 400 px scope instead of a clipped 25 NM ring."
        )
    }

    func testAircraftTrailLengthScalesWithGroundSpeed() {
        let slow = AircraftMarker.trailLengthPixels(groundSpeedKt: 120, rangeNM: 25, R: 180)
        let fast = AircraftMarker.trailLengthPixels(groundSpeedKt: 480, rangeNM: 25, R: 180)

        XCTAssertEqual(fast, slow * 4, accuracy: 0.001)
        XCTAssertGreaterThan(fast, slow)
    }

    func testAircraftTrailPointsBehindTrackDirection() {
        let northbound = AircraftMarker.trailEndPoint(trackDeg: 0, groundSpeedKt: 360, rangeNM: 25, R: 180)
        XCTAssertEqual(northbound.x, 0, accuracy: 0.001)
        XCTAssertGreaterThan(northbound.y, 0, "Northbound aircraft should trail south/down on screen.")

        let eastbound = AircraftMarker.trailEndPoint(trackDeg: 90, groundSpeedKt: 360, rangeNM: 25, R: 180)
        XCTAssertLessThan(eastbound.x, 0, "Eastbound aircraft should trail west/left on screen.")
        XCTAssertEqual(eastbound.y, 0, accuracy: 0.001)

        let southbound = AircraftMarker.trailEndPoint(trackDeg: 180, groundSpeedKt: 360, rangeNM: 25, R: 180)
        XCTAssertEqual(southbound.x, 0, accuracy: 0.001)
        XCTAssertLessThan(southbound.y, 0, "Southbound aircraft should trail north/up on screen.")

        let westbound = AircraftMarker.trailEndPoint(trackDeg: 270, groundSpeedKt: 360, rangeNM: 25, R: 180)
        XCTAssertGreaterThan(westbound.x, 0, "Westbound aircraft should trail east/right on screen.")
        XCTAssertEqual(westbound.y, 0, accuracy: 0.001)
    }

    func testRadarMapHeartAlignsToScopeCenter() {
        let mapSize = RadarMapUnderlay.mapSize(for: 178)
        let mapCenter = RadarMapUnderlay.mapCenter(cx: 200, cy: 190, mapSize: mapSize)
        let scale = mapSize.width / RadarMapUnderlay.imageSize.width
        let renderedHeart = CGPoint(
            x: mapCenter.x - RadarMapUnderlay.imageSize.width / 2 * scale + RadarMapUnderlay.herePoint.x * scale,
            y: mapCenter.y - RadarMapUnderlay.imageSize.height / 2 * scale + RadarMapUnderlay.herePoint.y * scale
        )

        XCTAssertEqual(renderedHeart.x, 200, accuracy: 0.001)
        XCTAssertEqual(renderedHeart.y, 190, accuracy: 0.001)
    }

    func testRadarMapCoversCircleEdgesForSupportedRanges() {
        let cx: CGFloat = 200
        let cy: CGFloat = 190
        let R: CGFloat = 178

        for range in [25, 50, 75, 100] {
            let frame = RadarMapUnderlay.mapFrame(cx: cx, cy: cy, R: R)

            XCTAssertLessThanOrEqual(frame.minX, cx - R, "Expected map to cover left edge for \(range) NM.")
            XCTAssertLessThanOrEqual(frame.minY, cy - R, "Expected map to cover upper edge for \(range) NM.")
            XCTAssertGreaterThanOrEqual(frame.maxX, cx + R, "Expected map to cover right edge for \(range) NM.")
            XCTAssertGreaterThanOrEqual(frame.maxY, cy + R, "Expected map to cover lower edge for \(range) NM.")
        }
    }

    func testRadarMapStylesDarkenGrayscaleAndFavorColorLegibility() {
        let grayscale = RadarMapUnderlay.style(isFullColor: false)
        let color = RadarMapUnderlay.style(isFullColor: true)

        XCTAssertLessThan(grayscale.brightness, color.brightness)
        XCTAssertGreaterThan(grayscale.washOpacity, 0.1)
        XCTAssertGreaterThan(color.opacity, 0.5)
        XCTAssertGreaterThan(color.saturation, 0.8)
        XCTAssertLessThan(color.washOpacity, 0.35)
    }
}

private extension CGImage {
    func containsAircraftPixels(nearX x: Int, y: Int, radius: Int = 8) -> Bool {
        let bytesPerPixel = 4
        let bytesPerRow = width * bytesPerPixel
        var pixels = [UInt8](repeating: 0, count: height * bytesPerRow)
        let colorSpace = CGColorSpaceCreateDeviceRGB()

        guard let context = CGContext(
            data: &pixels,
            width: width,
            height: height,
            bitsPerComponent: 8,
            bytesPerRow: bytesPerRow,
            space: colorSpace,
            bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
        ) else {
            return false
        }
        context.draw(self, in: CGRect(x: 0, y: 0, width: width, height: height))

        for py in max(0, y - radius)...min(height - 1, y + radius) {
            for px in max(0, x - radius)...min(width - 1, x + radius) {
                let offset = py * bytesPerRow + px * bytesPerPixel
                let r = pixels[offset]
                let g = pixels[offset + 1]
                let b = pixels[offset + 2]
                if r > 70, g > 150, b > 190, b >= g, g > r {
                    return true
                }
            }
        }

        return false
    }

    func containsRingPixels(nearX x: Int, y: Int, radius: Int = 3) -> Bool {
        let bytesPerPixel = 4
        let bytesPerRow = width * bytesPerPixel
        var pixels = [UInt8](repeating: 0, count: height * bytesPerRow)
        let colorSpace = CGColorSpaceCreateDeviceRGB()

        guard let context = CGContext(
            data: &pixels,
            width: width,
            height: height,
            bitsPerComponent: 8,
            bytesPerRow: bytesPerRow,
            space: colorSpace,
            bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
        ) else {
            return false
        }
        context.draw(self, in: CGRect(x: 0, y: 0, width: width, height: height))

        for py in max(0, y - radius)...min(height - 1, y + radius) {
            for px in max(0, x - radius)...min(width - 1, x + radius) {
                let offset = py * bytesPerRow + px * bytesPerPixel
                let r = pixels[offset]
                let g = pixels[offset + 1]
                let b = pixels[offset + 2]
                if g > 35, b > 45, g >= r, b >= r {
                    return true
                }
            }
        }

        return false
    }
}
