import json, os, sys

S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))


def out(value):
    print(json.dumps(value, default=str))


def fail(message, **extra):
    payload = {"error": message}
    payload.update(extra)
    print(json.dumps(payload, default=str))
    sys.exit(1)


def as_bool(v):
    return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")


def as_int(v, default=None):
    try:
        return int(v)
    except (TypeError, ValueError):
        return default


def as_list(v):
    if isinstance(v, list):
        return v
    if v in (None, ""):
        return []
    return [x.strip() for x in str(v).split(",") if x.strip()]


try:
    from pycti import OpenCTIApiClient
except ImportError as e:
    fail("The 'pycti' Python library is required for the OpenCTI integration. "
         "Install it on the execution host (engine): pip install pycti", detail=str(e))


def client():
    base = str(S.get("base_url", "")).strip().rstrip("/")
    api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
    return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")


TYPES_TO_OPENCTI = {
    "account": "User-Account", "domain": "Domain-Name", "email": "Email-Addr",
    "file-md5": "StixFile", "file-sha1": "StixFile", "file-sha256": "StixFile", "file": "StixFile",
    "host": "X-OpenCTI-Hostname", "ip": "IPv4-Addr", "ipv6": "IPv6-Addr",
    "registry key": "Windows-Registry-Key", "url": "Url",
}
OPENCTI_TO_TYPE = {
    "User-Account": "Account", "Domain-Name": "Domain", "Email-Addr": "Email", "StixFile": "File",
    "X-OpenCTI-Hostname": "Host", "IPv4-Addr": "IP", "IPv6-Addr": "IPv6",
    "Windows-Registry-Key": "Registry Key", "Url": "URL",
}


def build_observable_list(observable_list):
    result = []
    if "ALL" in observable_list:
        result = ["User-Account", "Domain-Name", "Email-Addr", "StixFile", "X-OpenCTI-Hostname",
                  "IPv4-Addr", "IPv6-Addr", "Windows-Registry-Key", "Url"]
    else:
        result = [TYPES_TO_OPENCTI.get(o.lower(), o) for o in observable_list]
    return result


def get_observables(client, observable_types, score=None, limit=500, last_run_id=None, search="", get_all=False):
    observable_type = build_observable_list(observable_types)
    filters = {
        "mode": "and",
        "filters": [{"key": "entity_type", "values": observable_type, "operator": "eq", "mode": "or"}],
        "filterGroups": [],
    }
    if score:
        filters["filters"].append({"key": "x_opencti_score", "values": score, "operator": "eq", "mode": "or"})
    return client.stix_cyber_observable.list(
        after=last_run_id, first=limit, withPagination=True, getAll=get_all, filters=filters, search=search
    )


observable_types = as_list(I.get("observable_types", "ALL")) or ["ALL"]
last_run_id = I.get("last_run_id")
limit = as_int(I.get("limit", 50), 50)
start = as_int(I.get("score_start", 0), 0)
end = as_int(I.get("score_end", 100), 100)
score = I.get("score")
search = I.get("search", "")
get_all = as_bool(I.get("all_results", "false"))

scores = None
if score:
    if str(score).lower() == "unknown":
        scores = [None]
    elif str(score).isdigit():
        scores = [score]
    else:
        fail("Invalid score was provided.")
elif start or end:
    scores = [str(n) for n in range(start, end + 1)]

try:
    raw = get_observables(
        client(),
        observable_types,
        score=scores,
        limit=limit,
        last_run_id=last_run_id,
        search=search,
        get_all=get_all,
    )
except Exception as e:
    fail("Can't list observables from OpenCTI.", detail=str(e))

last_run = None if get_all else raw.get("pagination", {}).get("endCursor")
observables_list = raw if get_all else raw.get("entities", [])

normalized = []
for o in observables_list:
    normalized.append({
        "type": OPENCTI_TO_TYPE.get(o["entity_type"], o["entity_type"]),
        "value": o.get("observable_value"),
        "id": o.get("id"),
        "createdBy": o.get("createdBy").get("id") if o.get("createdBy") else None,
        "score": o.get("x_opencti_score"),
        "description": o.get("x_opencti_description"),
        "labels": [l.get("value") for l in o.get("objectLabel", [])],
        "marking": [m.get("definition") for m in o.get("objectMarking", [])],
        "externalReferences": o.get("externalReferences"),
    })

out({"observables": normalized, "lastRunID": last_run})
