import json
import re

from test import conftest

import msgspec
import pytest
from pydantic import ValidationError

import csaf.cvss as cvss
from csaf.definitions import Products
from csaf.vulnerability import (
    Cwe,
    Entitlement,
    Remediation,
    RemediationCategory,
    RestartRequired,
    RestartRequiredCategory,
    Score,
    Threat,
    ThreatCategory,
    Vulnerability,
)
from csaf.cvss import CVSS31

DATA = {
    'baseScore': 10.0,
    'baseSeverity': 'CRITICAL',
    'vectorString': 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H',
    'version': '3.1',
}
JSON = json.dumps(DATA)


CVSS31_BASE_SEVERITY_LOG4J = 'CRITICAL'  # str(SeverityType.critical)


def test_vulnerability_empty():
    assert isinstance(Vulnerability(), Vulnerability)


def test_vulnerability_text():
    message = 'BaseModel.__init__() takes 1 positional argument but 3 were given'
    with pytest.raises(TypeError, match=re.escape(message)):
        _ = Vulnerability('some', 'text')  # type: ignore


def test_vulnerability_score_text():
    message = 'BaseModel.__init__() takes 1 positional argument but 2 were given'
    with pytest.raises(TypeError, match=re.escape(message)):
        _ = Score('text')  # type: ignore


def test_vulnerability_score_cvss31_log4j_cve_2021_44228():
    data = {
        'version': '3.1',  # str(CvssVersion.three_wun),
        'vectorString': conftest.CVSS31_VECTOR_STRING_LOG4J,
        'baseScore': conftest.CVSS31_BASE_SCORE_LOG4J,
        'baseSeverity': CVSS31_BASE_SEVERITY_LOG4J,
    }
    as_json = json.dumps(data)
    cvss_cve_2021_44228 = CVSS31.model_validate_json(as_json)
    message = '1 validation error for Score'
    with pytest.raises(ValidationError, match=message) as err:
        _ = Score(cvss_v3=cvss_cve_2021_44228, products=[])  # type: ignore
    assert '\nproducts\n  List should have at least 1 item after validation, not 0' in str(err.value)


def test_vulnerability_score_cvss31_log4j_cve_2021_44228_product_foo():
    data = {
        'version': '3.1',  # str(CvssVersion.three_wun),
        'vectorString': conftest.CVSS31_VECTOR_STRING_LOG4J,
        'baseScore': conftest.CVSS31_BASE_SCORE_LOG4J,
        'baseSeverity': CVSS31_BASE_SEVERITY_LOG4J,
    }
    # pr_ref = product.ReferenceTokenForProductInstance(value='log4j-123')
    products = Products(['log4j-123'])
    as_json = json.dumps(data)
    cvss_cve_2021_44228 = CVSS31.model_validate_json(as_json)
    score = Score(cvss_v3=cvss_cve_2021_44228, products=products)
    assert score.cvss_v3 == cvss_cve_2021_44228
    assert score.products == products


def test_vulnerability_score_loads_cvss31_log4j_cve_2021_44228_product_foo():
    score = Score.model_validate_json(json.dumps(conftest.VULNERABILITY_SCORE_LOG4J))
    assert msgspec.json.decode(score.model_dump_json()) == conftest.VULNERABILITY_SCORE_LOG4J


def test_vulnerability_cwe_text():
    message = 'BaseModel.__init__() takes 1 positional argument but 2 were given'
    with pytest.raises(TypeError, match=re.escape(message)):
        _ = Cwe('text')  # type: ignore


def test_vulnerability_cwe_352():
    data = {
        'id': conftest.CWE_ID_352,
        'name': conftest.CWE_NAME_352,
    }
    cwe_352 = Cwe(**data)
    assert cwe_352.id == conftest.CWE_ID_352
    assert cwe_352.name == conftest.CWE_NAME_352


def test_vulnerability_entitlement_none():
    message = '1 validation error for Entitlement'
    with pytest.raises(ValidationError, match=message) as err:
        _ = Entitlement()  # type: ignore
    assert 'Input should be a valid string' in str(err.value)


def test_vulnerability_entitlement_empty():
    message = '1 validation error for Entitlement'
    with pytest.raises(ValidationError, match=message) as err:
        _ = Entitlement('')
    assert 'String should have at least 1 characters' in str(err.value)


def test_vulnerability_entitlement_with_value():
    value = 'All people on this planet'
    all_people_entitled = Entitlement(value)
    assert all_people_entitled.root == value


def test_vulnerability_restart_req_by_rem_none():
    message = '1 validation error for RestartRequired'
    with pytest.raises(ValidationError, match=message) as err:
        _ = RestartRequired()  # type: ignore
    assert '\ncategory\n  Field required' in str(err.value)


def test_vulnerability_restart_req_by_rem_empty_category():
    message = '1 validation error for RestartRequired'
    with pytest.raises(ValidationError, match=message) as err:
        _ = RestartRequired(category='')  # type: ignore
    expected = (
        "\ncategory\n  Input should be 'connected', 'dependencies', 'machine', 'none',"
        " 'parent', 'service', 'system', 'vulnerable_component' or 'zone'"
    )
    assert expected in str(err.value)


def test_vulnerability_restart_req_by_rem_valid_category():
    category = RestartRequiredCategory.none
    restart_req_none = RestartRequired(category=category)
    assert restart_req_none.category == category


def test_vulnerability_restart_req_by_rem_valid_category_details_empty():
    category = RestartRequiredCategory.none
    message = '1 validation error for RestartRequired'
    with pytest.raises(ValidationError, match=message) as err:
        _ = RestartRequired(category=category, details='')
    assert '\ndetails\n  String should have at least 1 character' in str(err.value)


def test_vulnerability_restart_req_by_rem_valid_category_details_ok():
    category = RestartRequiredCategory.none
    details = 'And that is all I can say about that.'
    restart_req_none = RestartRequired(category=category, details=details)
    assert restart_req_none.category == category
    assert restart_req_none.details == details


def test_vulnerability_remediation_none():
    message = '2 validation errors for Remediation'
    with pytest.raises(ValidationError, match=message) as err:
        _ = Remediation()
    assert '\ncategory\n  Field required' in str(err.value)
    assert '\ndetails\n  Field required' in str(err.value)


def test_vulnerability_remediation_empty_category():
    message = '2 validation errors for Remediation'
    with pytest.raises(ValidationError, match=message) as err:
        _ = Remediation(category='')  # type: ignore
    expected = (
        "\ncategory\n  Input should be 'mitigation', 'no_fix_planned', 'none_available', 'vendor_fix' or 'workaround'"
    )
    assert expected in str(err.value)
    assert '\ndetails\n  Field required' in str(err.value)


def test_vulnerability_remediation_valid_category_details_empty():
    category = RemediationCategory.none_available
    message = '1 validation error for Remediation'
    with pytest.raises(ValidationError, match=message) as err:
        _ = Remediation(category=category, details='')
    assert '\ndetails\n  String should have at least 1 character' in str(err.value)


def test_vulnerability_remediation_req_by_rem_valid_category_details_ok():
    category = RemediationCategory.none_available
    details = 'And that is all I can say about that.'
    remediation_none = Remediation(category=category, details=details)
    assert remediation_none.category == category
    assert remediation_none.details == details


def test_vulnerability_threat_none():
    message = '2 validation errors for Threat'
    with pytest.raises(ValidationError, match=message) as err:
        _ = Threat()
    assert '\ncategory\n  Field required' in str(err.value)
    assert '\ndetails\n  Field required' in str(err.value)


def test_vulnerability_threat_empty_category():
    message = '2 validation errors for Threat'
    with pytest.raises(ValidationError, match=message) as err:
        _ = Threat(category='')  # type: ignore
    expected = "\ncategory\n  Input should be 'exploit_status', 'impact' or 'target_set'"
    assert expected in str(err.value)
    assert '\ndetails\n  Field required' in str(err.value)


def test_vulnerability_threat_valid_category_details_empty():
    category = ThreatCategory.exploit_status
    message = '1 validation error for Threat'
    with pytest.raises(ValidationError, match=message) as err:
        _ = Threat(category=category, details='')
    assert '\ndetails\n  String should have at least 1 character' in str(err.value)


def test_vulnerability_threat_req_by_rem_valid_category_details_ok():
    category = ThreatCategory.exploit_status
    details = 'And that is all I can say about that.'
    threat_exploit = Threat(category=category, details=details)
    assert threat_exploit.category == category
    assert threat_exploit.date is None
    assert threat_exploit.details == details
    assert threat_exploit.group_ids is None
    assert threat_exploit.product_ids is None


def test_vuln_cvss31_minimal():
    vector_string = 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H'
    expected_value = (
        f'{{"version":"3.1","vectorString":"{vector_string}","attackVector":null,'
        '"attackComplexity":null,"privilegesRequired":null,"userInteraction":null,"scope":null,'
        '"confidentialityImpact":null,"integrityImpact":null,"availabilityImpact":null,"baseScore":10.0,'
        '"baseSeverity":"CRITICAL","exploitCodeMaturity":null,"remediationLevel":null,"reportConfidence":null,'
        '"temporalScore":null,"temporalSeverity":null,"confidentialityRequirement":null,'
        '"integrityRequirement":null,"availabilityRequirement":null,"modifiedAttackVector":null,'
        '"modifiedAttackComplexity":null,"modifiedPrivilegesRequired":null,"modifiedUserInteraction":null,'
        '"modifiedScope":null,"modifiedConfidentialityImpact":null,"modifiedIntegrityImpact":null,'
        '"modifiedAvailabilityImpact":null,"environmentalScore":null,"environmentalSeverity":null}'
    )
    c31 = cvss.CVSS31.model_validate_json(JSON)
    assert c31.model_dump_json() == expected_value
    assert c31.vector_string == vector_string

    products = Products.model_validate_json('["sample"]')
    json_lines = c31.model_dump_json(indent=2).split('\n')
    json_rep_of_vs = [line for line in json_lines if 'vectorString' in line]
    assert len(json_rep_of_vs) == 1
    assert vector_string in json_rep_of_vs[0]
    assert '"vectorString":' in json_rep_of_vs[0]
    vul = Vulnerability(cve='CVE-2000-0001', scores=[Score(cvss_v3=c31, products=products)])
    v_json = vul.model_dump_json(exclude_unset=True, indent=2)
    expect_v = """\
{
  "cve": "CVE-2000-0001",
  "scores": [
    {
      "cvss_v3": {
        "version": "3.1",
        "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
        "baseScore": 10.0,
        "baseSeverity": "CRITICAL"
      },
      "products": [
        "sample"
      ]
    }
  ]
}"""
    assert v_json == expect_v
