import datetime as dt
import re

import pytest
import pytz

import stix2

from ...exceptions import InvalidValueError, PropertyPresenceError
from .constants import FAKE_TIME, MALWARE_ID, MALWARE_KWARGS

EXPECTED_MALWARE = """{
    "type": "malware",
    "spec_version": "2.1",
    "id": "malware--9c4638ec-f1de-4ddb-abf4-1b760417654e",
    "created": "2016-05-12T08:17:27.000Z",
    "modified": "2016-05-12T08:17:27.000Z",
    "name": "Cryptolocker",
    "malware_types": [
        "ransomware"
    ],
    "is_family": false
}"""


def test_malware_with_all_required_properties():
    now = dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)

    mal = stix2.v21.Malware(
        type="malware",
        id=MALWARE_ID,
        created=now,
        modified=now,
        malware_types=["ransomware"],
        name="Cryptolocker",
        is_family=False,
    )

    assert str(mal) == EXPECTED_MALWARE


def test_malware_autogenerated_properties(malware):
    assert malware.type == 'malware'
    assert malware.id == 'malware--00000000-0000-4000-8000-000000000001'
    assert malware.created == FAKE_TIME
    assert malware.modified == FAKE_TIME
    assert malware.malware_types == ['ransomware']
    assert malware.name == "Cryptolocker"

    assert malware['type'] == 'malware'
    assert malware['id'] == 'malware--00000000-0000-4000-8000-000000000001'
    assert malware['created'] == FAKE_TIME
    assert malware['modified'] == FAKE_TIME
    assert malware['malware_types'] == ['ransomware']
    assert malware['name'] == "Cryptolocker"


def test_malware_type_must_be_malware():
    with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
        stix2.v21.Malware(type='xxx', **MALWARE_KWARGS)

    assert excinfo.value.cls == stix2.v21.Malware
    assert excinfo.value.prop_name == "type"
    assert excinfo.value.reason == "must equal 'malware'."
    assert str(excinfo.value) == "Invalid value for Malware 'type': must equal 'malware'."


def test_malware_id_must_start_with_malware():
    with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
        stix2.v21.Malware(id='my-prefix--', **MALWARE_KWARGS)

    assert excinfo.value.cls == stix2.v21.Malware
    assert excinfo.value.prop_name == "id"
    assert excinfo.value.reason == "must start with 'malware--'."
    assert str(excinfo.value) == "Invalid value for Malware 'id': must start with 'malware--'."


def test_malware_required_properties():
    with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
        stix2.v21.Malware()

    assert excinfo.value.cls == stix2.v21.Malware
    assert excinfo.value.properties == ["is_family", "malware_types"]


def test_malware_required_property_name():
    with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
        stix2.v21.Malware(malware_types=['ransomware'])

    assert excinfo.value.cls == stix2.v21.Malware
    assert excinfo.value.properties == ["is_family"]


def test_cannot_assign_to_malware_attributes(malware):
    with pytest.raises(stix2.exceptions.ImmutableError) as excinfo:
        malware.name = "Cryptolocker II"

    assert str(excinfo.value) == "Cannot modify 'name' property in 'Malware' after creation."


def test_invalid_kwarg_to_malware():
    with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
        stix2.v21.Malware(my_custom_property="foo", **MALWARE_KWARGS)

    assert excinfo.value.cls == stix2.v21.Malware
    assert excinfo.value.properties == ['my_custom_property']
    assert str(excinfo.value) == "Unexpected properties for Malware: (my_custom_property)."


@pytest.mark.parametrize(
    "data", [
        EXPECTED_MALWARE,
        {
            "type": "malware",
            "spec_version": "2.1",
            "id": MALWARE_ID,
            "created": "2016-05-12T08:17:27.000Z",
            "modified": "2016-05-12T08:17:27.000Z",
            "malware_types": ["ransomware"],
            "name": "Cryptolocker",
            "is_family": False,
        },
    ],
)
def test_parse_malware(data):
    mal = stix2.parse(data)

    assert mal.type == 'malware'
    assert mal.spec_version == '2.1'
    assert mal.id == MALWARE_ID
    assert mal.created == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
    assert mal.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
    assert mal.malware_types == ['ransomware']
    assert mal.name == 'Cryptolocker'
    assert not mal.is_family


def test_parse_malware_invalid_labels():
    data = re.compile('\\[.+\\]', re.DOTALL).sub('1', EXPECTED_MALWARE)
    with pytest.raises(InvalidValueError) as excinfo:
        stix2.parse(data)
    assert "Invalid value for Malware 'malware_types'" in str(excinfo.value)


def test_parse_malware_kill_chain_phases():
    kill_chain = """
      "kill_chain_phases": [
        {
          "kill_chain_name": "lockheed-martin-cyber-kill-chain",
          "phase_name": "reconnaissance"
        }
      ]"""
    data = EXPECTED_MALWARE.replace('malware"', 'malware",%s' % kill_chain)
    mal = stix2.parse(data, version="2.1")
    assert mal.kill_chain_phases[0].kill_chain_name == "lockheed-martin-cyber-kill-chain"
    assert mal.kill_chain_phases[0].phase_name == "reconnaissance"
    assert mal['kill_chain_phases'][0]['kill_chain_name'] == "lockheed-martin-cyber-kill-chain"
    assert mal['kill_chain_phases'][0]['phase_name'] == "reconnaissance"


def test_parse_malware_clean_kill_chain_phases():
    kill_chain = """
      "kill_chain_phases": [
        {
          "kill_chain_name": "lockheed-martin-cyber-kill-chain",
          "phase_name": 1
        }
      ]"""
    data = EXPECTED_MALWARE.replace('2.1"', '2.1",%s' % kill_chain)
    mal = stix2.parse(data, version="2.1")
    assert mal['kill_chain_phases'][0]['phase_name'] == "1"


def test_malware_invalid_last_before_first():
    with pytest.raises(ValueError) as excinfo:
        stix2.v21.Malware(first_seen="2017-01-01T12:34:56.000Z", last_seen="2017-01-01T12:33:56.000Z", **MALWARE_KWARGS)

    assert "'last_seen' must be greater than or equal to 'first_seen'" in str(excinfo.value)


def test_malware_family_no_name():
    with pytest.raises(PropertyPresenceError):
        stix2.parse({
            "type": "malware",
            "id": MALWARE_ID,
            "spec_version": "2.1",
            "is_family": True,
            "malware_types": ["a type"],
        })


def test_malware_non_family_no_name():
    stix2.parse({
        "type": "malware",
        "id": MALWARE_ID,
        "spec_version": "2.1",
        "is_family": False,
        "malware_types": ["something"],
    })
