import datetime as dt
import re

import pytest
import pytz

import stix2
import stix2.exceptions

from .constants import LOCATION_ID

EXPECTED_LOCATION_1 = """{
    "type": "location",
    "spec_version": "2.1",
    "id": "location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64",
    "created": "2016-04-06T20:03:00.000Z",
    "modified": "2016-04-06T20:03:00.000Z",
    "latitude": 48.8566,
    "longitude": 2.3522
}"""

EXPECTED_LOCATION_1_REPR = "Location(" + " ".join(
    """
    type='location',
    spec_version='2.1',
    id='location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64',
    created='2016-04-06T20:03:00.000Z',
    modified='2016-04-06T20:03:00.000Z',
    latitude=48.8566,
    longitude=2.3522,
    revoked=False""".split(),
) + ")"

EXPECTED_LOCATION_2 = """{
    "type": "location",
    "spec_version": "2.1",
    "id": "location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64",
    "created": "2016-04-06T20:03:00.000Z",
    "modified": "2016-04-06T20:03:00.000Z",
    "region": "northern-america"
}
"""

EXPECTED_LOCATION_2_REPR = "Location(" + " ".join(
    """
    type='location',
    spec_version='2.1',
    id='location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64',
    created='2016-04-06T20:03:00.000Z',
    modified='2016-04-06T20:03:00.000Z',
    region='northern-america',
    revoked=False""".split(),
) + ")"


def test_location_with_some_required_properties():
    now = dt.datetime(2016, 4, 6, 20, 3, 0, tzinfo=pytz.utc)

    location = stix2.v21.Location(
        id=LOCATION_ID,
        created=now,
        modified=now,
        latitude=48.8566,
        longitude=2.3522,
    )

    assert location.serialize(pretty=True) == EXPECTED_LOCATION_1
    rep = re.sub(r"(\[|=| )u('|\"|\\\'|\\\")", r"\g<1>\g<2>", repr(location))
    assert rep == EXPECTED_LOCATION_1_REPR


@pytest.mark.parametrize(
    "data", [
        EXPECTED_LOCATION_2,
        {
            "type": "location",
            "spec_version": "2.1",
            "id": LOCATION_ID,
            "created": "2016-04-06T20:03:00.000Z",
            "modified": "2016-04-06T20:03:00.000Z",
            "region": "northern-america",
        },
    ],
)
def test_parse_location(data):
    location = stix2.parse(data, version="2.1")

    assert location.type == 'location'
    assert location.spec_version == '2.1'
    assert location.id == LOCATION_ID
    assert location.created == dt.datetime(2016, 4, 6, 20, 3, 0, tzinfo=pytz.utc)
    assert location.modified == dt.datetime(2016, 4, 6, 20, 3, 0, tzinfo=pytz.utc)
    assert location.region == 'northern-america'
    rep = re.sub(r"(\[|=| )u('|\"|\\\'|\\\")", r"\g<1>\g<2>", repr(location))
    assert rep == EXPECTED_LOCATION_2_REPR


@pytest.mark.parametrize(
    "data", [
        {
            "type": "location",
            "spec_version": "2.1",
            "id": LOCATION_ID,
            "created": "2016-04-06T20:03:00.000Z",
            "modified": "2016-04-06T20:03:00.000Z",
            "latitude": 90.01,
            "longitude": 0.0,
        },
        {
            "type": "location",
            "spec_version": "2.1",
            "id": LOCATION_ID,
            "created": "2016-04-06T20:03:00.000Z",
            "modified": "2016-04-06T20:03:00.000Z",
            "latitude": -90.1,
            "longitude": 0.0,
        },
    ],
)
def test_location_bad_latitude(data):
    with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
        stix2.parse(data)

    assert "Invalid value for Location 'latitude'" in str(excinfo.value)


@pytest.mark.parametrize(
    "data", [
        {
            "type": "location",
            "spec_version": "2.1",
            "id": LOCATION_ID,
            "created": "2016-04-06T20:03:00.000Z",
            "modified": "2016-04-06T20:03:00.000Z",
            "latitude": 80,
            "longitude": 180.1,
        },
        {
            "type": "location",
            "spec_version": "2.1",
            "id": LOCATION_ID,
            "created": "2016-04-06T20:03:00.000Z",
            "modified": "2016-04-06T20:03:00.000Z",
            "latitude": 80,
            "longitude": -180.1,
        },
    ],
)
def test_location_bad_longitude(data):
    with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
        stix2.parse(data)

    assert "Invalid value for Location 'longitude'" in str(excinfo.value)


@pytest.mark.parametrize(
    "data", [
        {
            "type": "location",
            "spec_version": "2.1",
            "id": LOCATION_ID,
            "created": "2016-04-06T20:03:00.000Z",
            "modified": "2016-04-06T20:03:00.000Z",
            "longitude": 175.7,
            "precision": 20,
        },
        {
            "type": "location",
            "spec_version": "2.1",
            "id": LOCATION_ID,
            "created": "2016-04-06T20:03:00.000Z",
            "modified": "2016-04-06T20:03:00.000Z",
            "latitude": 80,
            "precision": 20,
        },
    ],
)
def test_location_properties_missing_when_precision_is_present(data):
    with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
        stix2.parse(data)

    assert any(x in str(excinfo.value) for x in ("(latitude, precision)", "(longitude, precision)"))


@pytest.mark.parametrize(
    "data", [
        {
            "type": "location",
            "spec_version": "2.1",
            "id": LOCATION_ID,
            "created": "2016-04-06T20:03:00.000Z",
            "modified": "2016-04-06T20:03:00.000Z",
            "latitude": 18.468842,
            "longitude": -66.120711,
            "precision": -100.0,
        },
    ],
)
def test_location_negative_precision(data):
    with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
        stix2.parse(data)

    assert "Invalid value for Location 'precision'" in str(excinfo.value)


@pytest.mark.parametrize(
    "data,msg", [
        (
            {
                "type": "location",
                "spec_version": "2.1",
                "id": LOCATION_ID,
                "created": "2016-04-06T20:03:00.000Z",
                "modified": "2016-04-06T20:03:00.000Z",
                "latitude": 18.468842,
                "precision": 5.0,
            },
            "(longitude, precision) are not met.",
        ),
        (
            {
                "type": "location",
                "spec_version": "2.1",
                "id": LOCATION_ID,
                "created": "2016-04-06T20:03:00.000Z",
                "modified": "2016-04-06T20:03:00.000Z",
                "longitude": 160.7,
                "precision": 5.0,
            },
            "(latitude, precision) are not met.",
        ),
    ],
)
def test_location_latitude_dependency_missing(data, msg):
    with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
        stix2.parse(data)

    assert msg in str(excinfo.value)


@pytest.mark.parametrize(
    "data,msg", [
        (
            {
                "type": "location",
                "spec_version": "2.1",
                "id": LOCATION_ID,
                "created": "2016-04-06T20:03:00.000Z",
                "modified": "2016-04-06T20:03:00.000Z",
                "latitude": 18.468842,
            },
            "(longitude, latitude) are not met.",
        ),
        (
            {
                "type": "location",
                "spec_version": "2.1",
                "id": LOCATION_ID,
                "created": "2016-04-06T20:03:00.000Z",
                "modified": "2016-04-06T20:03:00.000Z",
                "longitude": 160.7,
            },
            "(latitude, longitude) are not met.",
        ),
    ],
)
def test_location_lat_or_lon_dependency_missing(data, msg):
    with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
        stix2.parse(data)

    assert msg in str(excinfo.value)


def test_location_complex_presence_constraint():
    with pytest.raises(stix2.exceptions.PropertyPresenceError):
        stix2.parse({
            "type": "location",
            "spec_version": "2.1",
            "id": LOCATION_ID,
        })


def test_google_map_url_long_lat_provided():
    expected_url = "https://www.google.com/maps/search/?api=1&query=41.862401%2C-87.616001"

    loc = stix2.v21.Location(
        latitude=41.862401,
        longitude=-87.616001,
    )

    loc_url = loc.to_maps_url()
    assert loc_url == expected_url


def test_google_map_url_multiple_props_no_long_lat_provided():
    expected_url = "https://www.google.com/maps/search/?api=1&query=1410+Museum+Campus+Drive%2C+Chicago%2C+IL+60605%2CUnited+States+of+America%2CNorth+America"
    now = dt.datetime(2019, 2, 7, 12, 34, 56, tzinfo=pytz.utc)

    loc = stix2.v21.Location(
        type="location",
        id=LOCATION_ID,
        created=now,
        modified=now,
        region="North America",
        country="United States of America",
        street_address="1410 Museum Campus Drive, Chicago, IL 60605",
        allow_custom=True,
    )

    loc_url = loc.to_maps_url()
    assert loc_url == expected_url


def test_google_map_url_multiple_props_and_long_lat_provided():
    expected_url = "https://www.google.com/maps/search/?api=1&query=41.862401%2C-87.616001"

    loc = stix2.v21.Location(
        region="northern-america",
        country="United States of America",
        street_address="1410 Museum Campus Drive, Chicago, IL 60605",
        latitude=41.862401,
        longitude=-87.616001,
    )

    loc_url = loc.to_maps_url()
    assert loc_url == expected_url


def test_map_url_invalid_map_engine_provided():
    loc = stix2.v21.Location(
        latitude=41.862401,
        longitude=-87.616001,
    )

    with pytest.raises(ValueError) as excinfo:
        loc.to_maps_url("Fake Maps")

    assert "is not a valid or currently-supported map engine" in str(excinfo.value)


def test_bing_map_url_long_lat_provided():
    expected_url = "https://bing.com/maps/default.aspx?where1=41.862401%2C-87.616001&lvl=16"

    loc = stix2.v21.Location(
        latitude=41.862401,
        longitude=-87.616001,
    )

    loc_url = loc.to_maps_url("Bing Maps")
    assert loc_url == expected_url


def test_bing_map_url_multiple_props_no_long_lat_provided():
    expected_url = "https://bing.com/maps/default.aspx?where1=1410+Museum+Campus+Drive%2C+Chicago%2C+IL+60605%2CUnited+States+of+America%2CNorth+America&lvl=16"

    loc = stix2.v21.Location(
        region="North America",
        country="United States of America",
        street_address="1410 Museum Campus Drive, Chicago, IL 60605",
        allow_custom=True,
    )

    loc_url = loc.to_maps_url("Bing Maps")
    assert loc_url == expected_url


def test_bing_map_url_multiple_props_and_long_lat_provided():
    expected_url = "https://bing.com/maps/default.aspx?where1=41.862401%2C-87.616001&lvl=16"

    loc = stix2.v21.Location(
        region="northern-america",
        country="United States of America",
        street_address="1410 Museum Campus Drive, Chicago, IL 60605",
        latitude=41.862401,
        longitude=-87.616001,
    )

    loc_url = loc.to_maps_url("Bing Maps")
    assert loc_url == expected_url
