//
//  GeoTests.swift
//  Navvo Maps SDK for iOS — unit tests for the coordinate marshallers,
//  polyline codec and JSON accessors (pure logic; no network, no map).
//

import XCTest
@testable import NavvoMaps

final class GeoTests: XCTestCase {

    func testPolylineRoundTrip() {
        let path = [
            LatLng(31.95, 35.91),
            LatLng(31.951, 35.912),
            LatLng(31.96, 35.92),
            LatLng(31.9, 35.8),
        ]
        let encoded = NavvoPolylineCodec.encode(path)
        let decoded = NavvoPolylineCodec.decode(encoded)
        XCTAssertEqual(decoded.count, path.count)
        for (expected, actual) in zip(path, decoded) {
            XCTAssertEqual(expected.latitude, actual.latitude, accuracy: 1e-5)
            XCTAssertEqual(expected.longitude, actual.longitude, accuracy: 1e-5)
        }
    }

    func testPolylineDecodeMalformedDoesNotCrash() {
        XCTAssertEqual(NavvoPolylineCodec.decode(""), [])
        _ = NavvoPolylineCodec.decode("_p~iF") // truncated input
    }

    func testWktParsing() {
        let point = LatLng(wkt: "POINT(35.8806 31.9461)")
        XCTAssertNotNil(point)
        XCTAssertEqual(point?.latitude ?? 0, 31.9461, accuracy: 1e-9)
        XCTAssertEqual(point?.longitude ?? 0, 35.8806, accuracy: 1e-9)
        XCTAssertNil(LatLng(wkt: "nonsense"))
        XCTAssertNil(LatLng(wkt: nil))
        XCTAssertNil(LatLng(wkt: ""))
    }

    func testBboxParameterIsWestSouthEastNorth() {
        let bounds = LatLngBounds(
            southwest: LatLng(31.90, 35.85),
            northeast: LatLng(31.99, 35.99)
        )
        XCTAssertEqual(bounds.bboxParameter, "35.85,31.9,35.99,31.99")
    }

    func testBoundsFitting() {
        let bounds = LatLngBounds(fitting: [
            LatLng(31.95, 35.91),
            LatLng(31.90, 35.99),
            LatLng(31.99, 35.85),
        ])
        XCTAssertNotNil(bounds)
        XCTAssertEqual(bounds?.southwest, LatLng(31.90, 35.85))
        XCTAssertEqual(bounds?.northeast, LatLng(31.99, 35.99))
        XCTAssertNil(LatLngBounds(fitting: []))
    }

    func testLatLngValidation() {
        XCTAssertNoThrow(try LatLng(31.95, 35.91).validated())
        XCTAssertThrowsError(try LatLng(91, 0).validated())
        XCTAssertThrowsError(try LatLng(0, 181).validated())
        XCTAssertThrowsError(try LatLng(.nan, 0).validated())
    }

    func testJSONAccessors() {
        let data = #"{"a":{"b":[1,"2",true,null]},"lat":"31.95"}"#.data(using: .utf8)!
        let json = NavvoJSON.parse(data)
        XCTAssertNotNil(json)
        XCTAssertEqual(json?["a"]?["b"]?[0]?.intValue, 1)
        XCTAssertEqual(json?["a"]?["b"]?[1]?.doubleValue ?? 0, 2, accuracy: 1e-12)
        XCTAssertEqual(json?["a"]?["b"]?[2]?.boolValue, true)
        XCTAssertEqual(json?["a"]?["b"]?[3], NavvoJSON.null)
        XCTAssertEqual(json?["lat"]?.doubleValue ?? 0, 31.95, accuracy: 1e-12)
        XCTAssertNil(json?["missing"])
    }

    func testRoutePlanParsesEngineEnvelope() {
        let shape = NavvoPolylineCodec.encode([LatLng(31.95, 35.91), LatLng(31.96, 35.92)])
        let payload: NavvoJSON = .object([
            "engine": .object([
                "available": .bool(true),
                "data": .object([
                    "trip": .object([
                        "summary": .object([
                            "length": .number(12.5),
                            "time": .number(900),
                        ]),
                        "legs": .array([
                            .object(["shape": .string(shape)])
                        ]),
                    ])
                ]),
            ])
        ])
        let plan = RoutePlan(raw: payload)
        XCTAssertEqual(plan.distanceKm ?? 0, 12.5, accuracy: 1e-9)
        XCTAssertEqual(plan.durationMinutes ?? 0, 15, accuracy: 1e-9)
        XCTAssertEqual(plan.geometry.count, 2)
    }

    func testPlaceCenterParsesWkt() {
        let place = PlaceDetails(raw: .object([
            "slug": .string("jordan-beans-cafe"),
            "center": .string("POINT(35.8806 31.9461)"),
            "rating": .string("4.7"),
        ]))
        XCTAssertEqual(place.center?.longitude ?? 0, 35.8806, accuracy: 1e-9)
        XCTAssertEqual(place.rating ?? 0, 4.7, accuracy: 1e-9)
    }

    func testStyleURLRequiresConfiguration() {
        NavvoMaps.reset()
        XCTAssertThrowsError(try NavvoMaps.styleURL(for: .standard))
        NavvoMaps.configure(apiKey: "nvvo_test")
        let url = try? NavvoMaps.styleURL(for: .dark, terrain: true)
        XCTAssertNotNil(url)
        let absolute = url?.absoluteString ?? ""
        XCTAssertTrue(absolute.contains("/tiles/style"))
        XCTAssertTrue(absolute.contains("style=dark"))
        XCTAssertTrue(absolute.contains("api_key=nvvo_test"))
        XCTAssertTrue(absolute.contains("terrain=1"))
        NavvoMaps.reset()
    }
}
