# Cypher query templates for Attack Paths operations
from tasks.jobs.attack_paths.config import (
    INTERNET_NODE_LABEL,
    PROWLER_FINDING_LABEL,
)


def render_cypher_template(template: str, replacements: dict[str, str]) -> str:
    """
    Render a Cypher query template by replacing placeholders.

    Placeholders use `__DOUBLE_UNDERSCORE__` format to avoid conflicts
    with Cypher syntax.
    """
    query = template
    for placeholder, value in replacements.items():
        query = query.replace(placeholder, value)
    return query


# Findings queries (used by findings.py)

ADD_RESOURCE_LABEL_TEMPLATE = """
    MATCH (account:__ROOT_LABEL__ {id: $provider_uid})-->(r)
    WHERE NOT r:__ROOT_LABEL__ AND NOT r:__RESOURCE_LABEL__
    WITH r LIMIT $batch_size
    SET r:__RESOURCE_LABEL__
    RETURN COUNT(r) AS labeled_count
"""

INSERT_FINDING_TEMPLATE = f"""
    UNWIND $findings_data AS finding_data

    OPTIONAL MATCH (resource_by_uid:__RESOURCE_LABEL__ {{__NODE_UID_FIELD__: finding_data.resource_uid}})
    OPTIONAL MATCH (resource_by_id:__RESOURCE_LABEL__ {{id: finding_data.resource_uid}})
        WHERE resource_by_uid IS NULL
    OPTIONAL MATCH (resource_by_short:__RESOURCE_LABEL__ {{id: finding_data.resource_short_uid}})
        WHERE resource_by_uid IS NULL AND resource_by_id IS NULL
    WITH finding_data,
         resource_by_uid,
         resource_by_id,
         head(collect(resource_by_short)) AS resource_by_short
    WITH finding_data,
         COALESCE(resource_by_uid, resource_by_id, resource_by_short) AS resource

    FOREACH (_ IN CASE WHEN resource IS NOT NULL THEN [1] ELSE [] END |
        MERGE (finding:{PROWLER_FINDING_LABEL} {{id: finding_data.id}})
            ON CREATE SET
                finding.id = finding_data.id,
                finding.uid = finding_data.uid,
                finding.inserted_at = finding_data.inserted_at,
                finding.updated_at = finding_data.updated_at,
                finding.first_seen_at = finding_data.first_seen_at,
                finding.scan_id = finding_data.scan_id,
                finding.delta = finding_data.delta,
                finding.status = finding_data.status,
                finding.status_extended = finding_data.status_extended,
                finding.severity = finding_data.severity,
                finding.check_id = finding_data.check_id,
                finding.check_title = finding_data.check_title,
                finding.muted = finding_data.muted,
                finding.muted_reason = finding_data.muted_reason,
                finding.firstseen = timestamp(),
                finding.lastupdated = $last_updated,
                finding._module_name = 'cartography:prowler',
                finding._module_version = $prowler_version
            ON MATCH SET
                finding.status = finding_data.status,
                finding.status_extended = finding_data.status_extended,
                finding.lastupdated = $last_updated
        MERGE (resource)-[rel:HAS_FINDING]->(finding)
            ON CREATE SET
                rel.firstseen = timestamp(),
                rel.lastupdated = $last_updated,
                rel._module_name = 'cartography:prowler',
                rel._module_version = $prowler_version
            ON MATCH SET
                rel.lastupdated = $last_updated
    )

    WITH sum(CASE WHEN resource IS NOT NULL THEN 1 ELSE 0 END) AS merged_count,
         sum(CASE WHEN resource IS NULL THEN 1 ELSE 0 END) AS dropped_count

    RETURN merged_count, dropped_count
"""

# Internet queries (used by internet.py)

CREATE_INTERNET_NODE = f"""
    MERGE (internet:{INTERNET_NODE_LABEL} {{id: 'Internet'}})
    ON CREATE SET
        internet.name = 'Internet',
        internet.firstseen = timestamp(),
        internet.lastupdated = $last_updated,
        internet._module_name = 'cartography:prowler',
        internet._module_version = $prowler_version
    ON MATCH SET
        internet.lastupdated = $last_updated
"""

CREATE_CAN_ACCESS_RELATIONSHIPS_TEMPLATE = f"""
    MATCH (account:__ROOT_LABEL__ {{id: $provider_uid}})-->(resource)
    WHERE resource.exposed_internet = true
    WITH resource
    MATCH (internet:{INTERNET_NODE_LABEL} {{id: 'Internet'}})
    MERGE (internet)-[r:CAN_ACCESS]->(resource)
    ON CREATE SET
        r.firstseen = timestamp(),
        r.lastupdated = $last_updated,
        r._module_name = 'cartography:prowler',
        r._module_version = $prowler_version
    ON MATCH SET
        r.lastupdated = $last_updated
    RETURN COUNT(r) AS relationships_merged
"""

# Sync queries (used by sync.py to fetch from the cartography temp Neo4j DB)
# The write side of sync lives in each sink (`api/attack_paths/sink/`).

NODE_FETCH_QUERY = """
    MATCH (n)
    WHERE id(n) > $last_id
    RETURN id(n) AS internal_id,
           elementId(n) AS element_id,
           labels(n) AS labels,
           properties(n) AS props
    ORDER BY internal_id
    LIMIT $batch_size
"""

RELATIONSHIPS_FETCH_QUERY = """
    MATCH ()-[r]->()
    WHERE id(r) > $last_id
    RETURN id(r) AS internal_id,
           type(r) AS rel_type,
           elementId(startNode(r)) AS start_element_id,
           elementId(endNode(r)) AS end_element_id,
           properties(r) AS props
    ORDER BY internal_id
    LIMIT $batch_size
"""
