import json
from typing import Any, Dict, List

from brox.model import AssetChange, Capability, CapabilityChange, CapabilityDiff
from brox.diff.risk import assess_risk
from brox.scan.normalize import is_scope_wider


def compute_diff(base_bom: Dict[str, Any], head_bom: Dict[str, Any]) -> CapabilityDiff:
    base_ref = "base"
    head_ref = "head"
    base_hash = extract_bom_hash(base_bom)
    head_hash = extract_bom_hash(head_bom)

    base_components = {c["bom-ref"]: c for c in base_bom.get("components", [])}
    head_components = {c["bom-ref"]: c for c in head_bom.get("components", [])}

    assets_added = []
    assets_removed = []
    assets_changed = []

    for bom_ref, component in head_components.items():
        if bom_ref not in base_components:
            kind = get_component_kind(component)
            if kind and kind != "repo":
                assets_added.append(AssetChange(id=bom_ref, kind=kind))

    for bom_ref, component in base_components.items():
        if bom_ref not in head_components:
            kind = get_component_kind(component)
            if kind and kind != "repo":
                assets_removed.append(AssetChange(id=bom_ref, kind=kind))

    for bom_ref in base_components:
        if bom_ref in head_components:
            base_comp = base_components[bom_ref]
            head_comp = head_components[bom_ref]
            kind = get_component_kind(head_comp)

            base_hash_prop = get_property(base_comp, "brox.hash.content")
            head_hash_prop = get_property(head_comp, "brox.hash.content")

            if base_hash_prop and head_hash_prop and base_hash_prop != head_hash_prop:
                if kind and kind != "repo":
                    assets_changed.append(AssetChange(id=bom_ref, kind=kind, change="content_hash"))

    base_capabilities = extract_capabilities(base_bom)
    head_capabilities = extract_capabilities(head_bom)

    capabilities_added = []
    capabilities_removed = []
    capabilities_widened = []

    for cap in head_capabilities:
        if not find_capability(base_capabilities, cap):
            capabilities_added.append(
                CapabilityChange(
                    capability=cap.name,
                    scope=cap.scope,
                    asset=cap.asset,
                    evidence=cap.evidence,
                )
            )

    for cap in base_capabilities:
        if not find_capability(head_capabilities, cap):
            capabilities_removed.append(
                CapabilityChange(
                    capability=cap.name,
                    scope=cap.scope,
                    asset=cap.asset,
                    evidence=cap.evidence,
                )
            )

    for head_cap in head_capabilities:
        for base_cap in base_capabilities:
            if (
                head_cap.name == base_cap.name
                and head_cap.asset == base_cap.asset
                and head_cap.scope != base_cap.scope
                and is_scope_wider(base_cap.scope, head_cap.scope)
            ):
                capabilities_widened.append(
                    CapabilityChange(
                        capability=head_cap.name,
                        asset=head_cap.asset,
                        from_scope=base_cap.scope,
                        to_scope=head_cap.scope,
                        scope=head_cap.scope,
                        evidence=head_cap.evidence,
                    )
                )

    risk = assess_risk(assets_added, assets_changed, capabilities_added, capabilities_widened)

    diff = CapabilityDiff(
        base_ref=base_ref,
        base_bom_hash=base_hash,
        head_ref=head_ref,
        head_bom_hash=head_hash,
        assets_added=assets_added,
        assets_removed=assets_removed,
        assets_changed=assets_changed,
        capabilities_added=capabilities_added,
        capabilities_removed=capabilities_removed,
        capabilities_widened=capabilities_widened,
        risk=risk,
    )

    return diff


def extract_bom_hash(bom: Dict[str, Any]) -> str:
    metadata = bom.get("metadata", {})
    properties = metadata.get("properties", [])
    for prop in properties:
        if prop.get("name") == "brox.bom.hash":
            return prop.get("value", "unknown")
    return "unknown"


def get_component_kind(component: Dict[str, Any]) -> str:
    properties = component.get("properties", [])
    for prop in properties:
        if prop.get("name") == "brox.ai.asset.kind":
            return prop.get("value", "unknown")
    return "unknown"


def get_property(component: Dict[str, Any], prop_name: str) -> str:
    properties = component.get("properties", [])
    for prop in properties:
        if prop.get("name") == prop_name:
            return prop.get("value", "")
    return ""


def extract_capabilities(bom: Dict[str, Any]) -> List[Capability]:
    capabilities = []

    services = bom.get("services", [])
    for service in services:
        if service.get("name") == "brox.ai.capabilities":
            properties = service.get("properties", [])
            for prop in properties:
                if prop.get("name") == "brox.capability.record":
                    cap = parse_capability_record(prop.get("value", ""))
                    if cap:
                        capabilities.append(cap)

    return capabilities


def parse_capability_record(record: str) -> Capability:
    try:
        data = json.loads(record)
        if "cap" in data and "scope" in data and "asset" in data:
            return Capability(
                name=data["cap"],
                scope=data["scope"],
                asset=data["asset"],
                evidence=data.get("evidence", []),
            )
    except (json.JSONDecodeError, KeyError):
        parts = {}
        for part in record.split(";"):
            if "=" in part:
                key, value = part.split("=", 1)
                parts[key] = value

        if "cap" in parts and "scope" in parts and "asset" in parts:
            if "evidence" in parts:
                evidence = parts.get("evidence", "").split(";")
            else:
                evidence = []

            if not isinstance(evidence, list):
                evidence = [evidence]

            return Capability(
                name=parts["cap"],
                scope=parts["scope"],
                asset=parts["asset"],
                evidence=evidence,
            )

    return None


def find_capability(capabilities: List[Capability], target: Capability) -> bool:
    for cap in capabilities:
        if (
            cap.name == target.name
            and cap.scope == target.scope
            and cap.asset == target.asset
        ):
            return True
    return False
