"""
Cognite Function for SHACL validation of instances with auto-loading of references.

This function validates incoming DMS instances against SHACL rules,
automatically loading referenced instances as needed based on sh:node constraints.

Validation results are always posted to CDF Records API for data quality tracking.
"""

from typing import Any

from common.records_api import post_validation_results_to_records
from common.shacl_utils import load_shacl_from_file


def handle_instance_validation(data: dict[str, Any], client) -> dict[str, Any]:
    """
    Validate instances with SHACL rules including auto-loading of referenced instances.

    This handler extracts instances from the nested Cognite Functions structure
    and passes a simple list to NEAT for validation.

    Args:
        data: Input data containing:
            - instances: Dict with instance items (e.g., {"items": {"n": [...], "e": [...]}})
            - shacl_rules: SHACL rules as Turtle string (use this OR shacl_rules_file_external_id)
            - shacl_rules_file_external_id: External ID of a CDF File containing SHACL rules
            - datamodel_space: Space of the data model
            - datamodel_external_id: External ID of the data model
            - datamodel_version: Version of the data model
            - auto_load_depth: (optional) Maximum depth for auto-loading (default: 2)
            - verbose: (optional) Print progress messages (default: True)
            - job_run_id: (optional) Unique job run ID for records
            - records_config: Config for Records API posting:
                - stream_id: Stream ID for records
                - rule_set_id: Rule set identifier
                - rule_set_version: Rule set version
                - records_space: (optional) Space for records (default: "dataQuality")
                - records_container: (optional) Container name (default: "DataQualityValidationRecord")
        client: Cognite client instance

    Returns:
        Dict with validation results:
            - conforms: Boolean indicating if validation passed
            - violations: List of violation details
            - report_text: Human-readable report
            - instance_count: Number of instances validated
            - records_posted: Number of records posted to Records API
    """
    import time

    from cognite_data_quality._neat import NeatSession

    print("=" * 80)
    print("SHACL VALIDATION WITH AUTO-LOADING")
    print("=" * 80)

    # Extract parameters
    instances_data = data.get("instances", {})
    shacl_rules = data.get("shacl_rules")
    shacl_rules_file_external_id = data.get("shacl_rules_file_external_id")
    datamodel_space = data.get("datamodel_space")
    datamodel_external_id = data.get("datamodel_external_id")
    datamodel_version = data.get("datamodel_version")
    auto_load_depth = data.get("auto_load_depth", 2)
    verbose = data.get("verbose", True)

    # Records API parameters
    job_run_id = data.get("job_run_id") or f"job_{int(time.time())}"
    records_config = data.get("records_config", {})

    # Load SHACL rules from CDF File if external ID is provided
    if shacl_rules_file_external_id and not shacl_rules:
        try:
            print(f"Loading SHACL rules from CDF File: {shacl_rules_file_external_id}")
            shacl_rules = load_shacl_from_file(client, shacl_rules_file_external_id)
            print(f"  Loaded {len(shacl_rules)} characters from file")
        except Exception as e:
            return {
                "conforms": False,
                "error": f"Failed to load SHACL rules from file '{shacl_rules_file_external_id}': {e!s}",
                "violations": [],
            }

    # Validate required parameters
    if not shacl_rules:
        return {
            "conforms": False,
            "error": "Missing required parameter: shacl_rules or shacl_rules_file_external_id",
            "violations": [],
        }

    if not datamodel_space or not datamodel_external_id or not datamodel_version:
        return {
            "conforms": False,
            "error": "Missing required parameters: datamodel_space, datamodel_external_id, datamodel_version",
            "violations": [],
        }

    # Extract instances from the nested Cognite Functions structure
    # Input: {"items": {"n": [...], "e": [...]}}
    # Output: Simple list of instance dicts for NEAT
    items = instances_data.get("items", {})
    all_instances = []

    if isinstance(items, dict):
        # Collect all instances from all keys (n=nodes, e=edges, etc.)
        for key, value in items.items():
            if isinstance(value, list):
                all_instances.extend(value)
                if verbose:
                    print(f"Found {len(value)} instances in key '{key}'")
    elif isinstance(items, list):
        # Already a simple list
        all_instances = items

    # Filter out deleted instances (those with deletedTime set)
    # deletedTime indicates a delete operation from sync/subscriptions
    instances = []
    deleted_count = 0
    for inst in all_instances:
        if inst.get("deletedTime"):
            deleted_count += 1
        else:
            instances.append(inst)

    if deleted_count > 0 and verbose:
        print(f"Skipped {deleted_count} deleted instances (have deletedTime)")

    if not instances:
        return {"conforms": True, "message": "No instances to validate", "instance_count": 0, "violations": []}

    print(f"Validating {len(instances)} instances...")
    print(f"Data model: {datamodel_space}/{datamodel_external_id}/{datamodel_version}")
    print(f"Auto-load depth: {auto_load_depth}")

    # Initialize NEAT session
    neat = NeatSession(client, verbose=False)

    try:
        # Validate with auto-loading
        conforms, report_graph, _report_text = neat.validate_instances.with_shacl(
            instances=instances,
            shacl_rules=shacl_rules,
            datamodel_space=datamodel_space,
            datamodel_external_id=datamodel_external_id,
            datamodel_version=datamodel_version,
            auto_load_depth=auto_load_depth,
            verbose=verbose,
        )

        # Extract violations from report graph
        violations = []
        if report_graph:
            from rdflib import Graph, Namespace

            if not isinstance(report_graph, Graph):
                # pyshacl caught a ValidationFailure internally and returned the exception
                # object as report_graph.  Create one synthetic violation per instance so
                # Records API reflects that these instances could not be validated.
                engine_error = str(report_graph)
                print(f"WARNING: SHACL engine failure: {engine_error}")
                for inst in instances:
                    inst_space = inst.get("space", "")
                    inst_ext_id = inst.get("externalId", "")
                    violations.append(
                        {
                            "focusNode": f"http://purl.org/cognite/{inst_space}/{inst_ext_id}",
                            "sourceConstraintComponent": "SHACLEngineFailure",
                            "resultMessage": f"SHACL engine failure prevented validation: {engine_error}",
                            "resultSeverity": "Violation",
                        }
                    )
            else:
                SH = Namespace("http://www.w3.org/ns/shacl#")

                for result in report_graph.subjects(predicate=None, object=SH.ValidationResult):
                    violation = {}
                    for pred, obj in report_graph.predicate_objects(subject=result):
                        pred_name = str(pred).split("#")[-1]
                        violation[pred_name] = str(obj)
                    violations.append(violation)

        # Print all violations
        print("=" * 80)
        if conforms:
            print("VALIDATION PASSED")
        else:
            print("VALIDATION FAILED")
        print("=" * 80)
        print(f"Instances validated: {len(instances)}")
        print(f"Violations found: {len(violations)}")

        if violations:
            print("--- ALL VIOLATIONS ---")
            for i, v in enumerate(violations, 1):
                print(f"  Violation {i}:")
                print(f"    Focus Node: {v.get('focusNode', 'N/A')}")
                print(f"    Message: {v.get('resultMessage', 'N/A')}")
                print(f"    Severity: {v.get('resultSeverity', 'N/A').split('#')[-1]}")
                print(f"    Source: {v.get('sourceConstraintComponent', 'N/A').split('#')[-1]}")
                if "value" in v:
                    print(f"    Value: {v.get('value', 'N/A')}")
                if "resultPath" in v:
                    print(f"    Path: {v.get('resultPath', 'N/A')}")

        print("=" * 80)

        # Build result — violations are already posted to Records API; return counters only
        result = {
            "conforms": conforms,
            "violation_count": len(violations),
            "instance_count": len(instances),
        }

        # Always post to Records API (even for 0 violations - to record that validation passed)
        stream_id = records_config.get("stream_id")
        rule_set_id = records_config.get("rule_set_id")
        rule_set_version = records_config.get("rule_set_version")

        if stream_id and rule_set_id and rule_set_version:
            print("=" * 80)
            print("POSTING TO RECORDS API")
            print("=" * 80)

            # Namespace base used by NEAT for URI generation
            namespace_base = "http://purl.org/cognite/"

            posted_count, record_errors = post_validation_results_to_records(
                client=client,
                all_instances=instances,
                violations=violations,
                job_run_id=job_run_id,
                stream_id=stream_id,
                rule_set_id=rule_set_id,
                rule_set_version=rule_set_version,
                namespace_base=namespace_base,
                records_space=records_config.get("records_space", "dataQuality"),
                records_container=records_config.get("records_container", "DataQualityValidationRecord"),
            )

            print(f"  Posted {posted_count} records")
            if record_errors:
                print(f"  WARNING: {len(record_errors)} record(s) failed to post:")
                for err in record_errors[:5]:  # Show first 5 errors
                    print(f"    - {err['instance']}: {err['error']}")
                if len(record_errors) > 5:
                    print(f"    ... and {len(record_errors) - 5} more")

                # Fail if posting failure rate exceeds threshold
                failure_rate = len(record_errors) / len(instances)
                failure_threshold = 0.1  # 10% threshold
                if failure_rate > failure_threshold:
                    raise RuntimeError(
                        f"Failed to post {len(record_errors)}/{len(instances)} records "
                        f"({failure_rate:.1%} failure rate exceeds {failure_threshold:.1%} threshold). "
                        f"Validation results cannot be persisted reliably. "
                        f"This is likely a transient API issue - the validation should be retried."
                    )

            result["records_posted"] = posted_count
            result["records_error_count"] = len(record_errors)
        else:
            print("WARNING: Missing required records_config (stream_id, rule_set_id, rule_set_version)")

        return result

    except Exception as e:
        import traceback

        error_msg = f"Validation error: {e!s}\n{traceback.format_exc()}"
        print(f"ERROR: {error_msg}")
        return {"conforms": False, "error": error_msg, "violation_count": 0, "instance_count": len(instances)}
