import XCTest
@testable import TestApp

final class GeoJSONAreaUtilTests: XCTestCase {

    // ~1m latitude step. mPerDegLat = 111_320, so a square of side
    // (1 / 111_320) degrees has area ~1 m^2 ~= 10.7639 sq ft.
    private static let degForOneMeter = 1.0 / 111_320.0

    func testSquareFeet_polygonAtEquator_oneSquareMeter() {
        let d = Self.degForOneMeter
        let ring: [[NSNumber]] = [
            [NSNumber(value: 0.0), NSNumber(value: 0.0)],
            [NSNumber(value: d),   NSNumber(value: 0.0)],
            [NSNumber(value: d),   NSNumber(value: d)],
            [NSNumber(value: 0.0), NSNumber(value: d)],
            [NSNumber(value: 0.0), NSNumber(value: 0.0)],
        ]
        let coords: [Any] = [ring]

        guard let sqft = GeoJSONAreaUtil.squareFeet(from: coords) else {
            return XCTFail("expected non-nil area for valid polygon")
        }
        // At the equator cos(lat)=1, so the equirectangular projection is
        // exact: 1 m^2 -> 10.7639 sq ft. Allow 1% slop for FP drift.
        XCTAssertEqual(sqft, 10.7639, accuracy: 10.7639 * 0.01)
    }

    func testSquareFeet_multiPolygon_sumsAllParts() {
        let d = Self.degForOneMeter
        func unitSquare(originLng: Double, originLat: Double) -> [[NSNumber]] {
            [
                [NSNumber(value: originLng),     NSNumber(value: originLat)],
                [NSNumber(value: originLng + d), NSNumber(value: originLat)],
                [NSNumber(value: originLng + d), NSNumber(value: originLat + d)],
                [NSNumber(value: originLng),     NSNumber(value: originLat + d)],
                [NSNumber(value: originLng),     NSNumber(value: originLat)],
            ]
        }
        // Two disjoint 1m^2 squares -> ~21.5278 sq ft total.
        let multi: [Any] = [
            [unitSquare(originLng: 0.0, originLat: 0.0)],
            [unitSquare(originLng: 1.0, originLat: 0.0)],
        ]
        guard let sqft = GeoJSONAreaUtil.squareFeet(from: multi) else {
            return XCTFail("expected non-nil area for multipolygon")
        }
        XCTAssertEqual(sqft, 10.7639 * 2.0, accuracy: 10.7639 * 0.02)
    }

    func testSquareFeet_holeIsSubtracted() {
        let d = Self.degForOneMeter
        let outer: [[NSNumber]] = [
            [NSNumber(value: 0.0),     NSNumber(value: 0.0)],
            [NSNumber(value: d * 4.0), NSNumber(value: 0.0)],
            [NSNumber(value: d * 4.0), NSNumber(value: d * 4.0)],
            [NSNumber(value: 0.0),     NSNumber(value: d * 4.0)],
            [NSNumber(value: 0.0),     NSNumber(value: 0.0)],
        ]
        let hole: [[NSNumber]] = [
            [NSNumber(value: d),       NSNumber(value: d)],
            [NSNumber(value: d * 2.0), NSNumber(value: d)],
            [NSNumber(value: d * 2.0), NSNumber(value: d * 2.0)],
            [NSNumber(value: d),       NSNumber(value: d * 2.0)],
            [NSNumber(value: d),       NSNumber(value: d)],
        ]
        let coords: [Any] = [outer, hole]
        guard let sqft = GeoJSONAreaUtil.squareFeet(from: coords) else {
            return XCTFail("expected non-nil area with hole")
        }
        // outer 16 m^2 - inner 1 m^2 = 15 m^2 -> ~161.46 sq ft.
        XCTAssertEqual(sqft, 10.7639 * 15.0, accuracy: 10.7639 * 0.05)
    }

    func testSquareFeet_invalidShape_returnsNil() {
        // Not a list of rings of [lng,lat] pairs.
        let bogus: [Any] = ["not", "geojson"]
        XCTAssertNil(GeoJSONAreaUtil.squareFeet(from: bogus))
    }

    func testSquareFeet_degeneratePolygon_returnsNil() {
        // Fewer than 3 points -> zero area -> nil.
        let ring: [[NSNumber]] = [
            [NSNumber(value: 0.0), NSNumber(value: 0.0)],
            [NSNumber(value: 1.0), NSNumber(value: 0.0)],
        ]
        XCTAssertNil(GeoJSONAreaUtil.squareFeet(from: [ring]))
    }
}
