"""Azure Data Factory project processor for extracting and analyzing metadata."""

import json
from pathlib import Path
from typing import Any, Dict, List, Optional


ADF_RESOURCE_DIRS = ["pipeline", "dataset", "linkedService", "trigger", "dataflow"]


def parse_adf_json(json_path: str) -> Dict[str, Any]:
    """Read and parse a single ADF resource JSON file.

    Args:
        json_path: Path to the JSON file

    Returns:
        Parsed JSON as a dictionary

    Raises:
        ValueError: If the path is invalid or not valid JSON
    """
    p = Path(json_path).resolve()

    if not p.exists():
        raise FileNotFoundError(
            f"ADF JSON file not found: {json_path}\n"
            f"Resolved to: {p}\n"
            f"Current working directory: {Path.cwd()}"
        )

    if not p.is_file() or p.suffix.lower() != ".json":
        raise ValueError(f"Invalid JSON path: {json_path}")

    raw = p.read_text(encoding="utf-8")
    return json.loads(raw)


def discover_adf_resources(directory: str) -> Dict[str, List[Dict[str, Any]]]:
    """Scan a directory for ADF resource folders and collect all JSON files.

    Looks for standard ADF subdirectories: pipeline/, dataset/,
    linkedService/, trigger/, dataflow/. Also collects any loose
    JSON files at the top level.

    Args:
        directory: Path to the ADF project directory

    Returns:
        Dictionary mapping resource type to list of parsed JSON objects
    """
    dir_path = Path(directory).resolve()

    if not dir_path.exists() or not dir_path.is_dir():
        raise ValueError(f"Invalid ADF project directory: {directory}")

    resources: Dict[str, List[Dict[str, Any]]] = {
        "pipelines": [],
        "datasets": [],
        "linkedServices": [],
        "triggers": [],
        "dataflows": [],
    }

    dir_map = {
        "pipeline": "pipelines",
        "dataset": "datasets",
        "linkedService": "linkedServices",
        "linkedservice": "linkedServices",
        "trigger": "triggers",
        "dataflow": "dataflows",
    }

    for subdir in dir_path.iterdir():
        if subdir.is_dir() and subdir.name.lower() in [d.lower() for d in ADF_RESOURCE_DIRS]:
            key = dir_map.get(subdir.name, dir_map.get(subdir.name.lower()))
            if key:
                for json_file in sorted(subdir.glob("*.json")):
                    try:
                        data = json.loads(json_file.read_text(encoding="utf-8"))
                        data["_source_file"] = json_file.name
                        resources[key].append(data)
                    except (json.JSONDecodeError, OSError):
                        continue

    if all(len(v) == 0 for v in resources.values()):
        for json_file in sorted(dir_path.rglob("*.json")):
            try:
                data = json.loads(json_file.read_text(encoding="utf-8"))
                _classify_loose_json(data, resources, json_file.name)
            except (json.JSONDecodeError, OSError):
                continue

    return resources


def _classify_loose_json(data: Dict[str, Any], resources: Dict[str, List], filename: str) -> None:
    """Classify a loose JSON file into the appropriate resource category."""
    data["_source_file"] = filename
    resource_type = data.get("type", "").lower()

    if "pipeline" in resource_type or "activities" in data.get("properties", {}):
        resources["pipelines"].append(data)
    elif "dataset" in resource_type:
        resources["datasets"].append(data)
    elif "linkedservice" in resource_type:
        resources["linkedServices"].append(data)
    elif "trigger" in resource_type:
        resources["triggers"].append(data)
    elif "dataflow" in resource_type:
        resources["dataflows"].append(data)
    else:
        props = data.get("properties", {})
        if "activities" in props:
            resources["pipelines"].append(data)
        elif "linkedServiceName" in props:
            resources["datasets"].append(data)
        elif "typeProperties" in props and "connectionString" in props.get("typeProperties", {}):
            resources["linkedServices"].append(data)


def extract_grading_info(resources: Dict[str, List[Dict[str, Any]]]) -> Dict[str, Any]:
    """Extract structured grading information from parsed ADF resources.

    Args:
        resources: Dictionary from discover_adf_resources

    Returns:
        Dictionary containing structured info about pipelines, datasets,
        linked services, triggers, dataflows, and summary statistics
    """
    grading_info: Dict[str, Any] = {
        "pipelines": [],
        "datasets": [],
        "linked_services": [],
        "triggers": [],
        "dataflows": [],
        "summary": {},
    }

    for pipeline in resources.get("pipelines", []):
        props = pipeline.get("properties", {})
        activities = []

        for activity in props.get("activities", []):
            act_info: Dict[str, Any] = {
                "name": activity.get("name"),
                "type": activity.get("type"),
            }

            depends = activity.get("dependsOn", [])
            if depends:
                act_info["depends_on"] = [
                    {
                        "activity": d.get("activity"),
                        "conditions": d.get("dependencyConditions", []),
                    }
                    for d in depends
                ]

            type_props = activity.get("typeProperties", {})
            if type_props:
                if "source" in type_props:
                    act_info["source_type"] = type_props["source"].get("type")
                if "sink" in type_props:
                    act_info["sink_type"] = type_props["sink"].get("type")
                if "dataset" in type_props:
                    act_info["dataset"] = type_props["dataset"].get("referenceName")

            inputs = activity.get("inputs", [])
            outputs = activity.get("outputs", [])
            if inputs:
                act_info["inputs"] = [i.get("referenceName") for i in inputs]
            if outputs:
                act_info["outputs"] = [o.get("referenceName") for o in outputs]

            activities.append(act_info)

        pipeline_info = {
            "name": pipeline.get("name") or props.get("name", "Unknown"),
            "activities": activities,
            "parameters": list((props.get("parameters") or {}).keys()),
            "variables": list((props.get("variables") or {}).keys()),
            "source_file": pipeline.get("_source_file", ""),
        }
        grading_info["pipelines"].append(pipeline_info)

    for dataset in resources.get("datasets", []):
        props = dataset.get("properties", {})
        ds_info = {
            "name": dataset.get("name") or props.get("name", "Unknown"),
            "type": props.get("type", "Unknown"),
            "linked_service": _get_ref_name(props.get("linkedServiceName")),
            "parameters": list((props.get("parameters") or {}).keys()),
            "source_file": dataset.get("_source_file", ""),
        }

        type_props = props.get("typeProperties", {})
        if "location" in type_props:
            loc = type_props["location"]
            ds_info["location_type"] = loc.get("type")
            ds_info["file_name"] = loc.get("fileName")
            ds_info["folder_path"] = loc.get("folderPath")
        if "tableName" in type_props:
            ds_info["table_name"] = type_props["tableName"]

        schema = props.get("schema", [])
        if schema and isinstance(schema, list):
            ds_info["schema_columns"] = [
                {"name": col.get("name"), "type": col.get("type")}
                for col in schema
                if isinstance(col, dict)
            ]

        grading_info["datasets"].append(ds_info)

    for ls in resources.get("linkedServices", []):
        props = ls.get("properties", {})
        ls_info = {
            "name": ls.get("name") or props.get("name", "Unknown"),
            "type": props.get("type", "Unknown"),
            "source_file": ls.get("_source_file", ""),
        }

        type_props = props.get("typeProperties", {})
        if "url" in type_props:
            ls_info["url"] = type_props["url"]
        if "accountName" in type_props:
            ls_info["account_name"] = type_props["accountName"]
        if "databaseName" in type_props:
            ls_info["database_name"] = type_props["databaseName"]

        grading_info["linked_services"].append(ls_info)

    for trigger in resources.get("triggers", []):
        props = trigger.get("properties", {})
        trig_info = {
            "name": trigger.get("name") or props.get("name", "Unknown"),
            "type": props.get("type", "Unknown"),
            "source_file": trigger.get("_source_file", ""),
        }

        type_props = props.get("typeProperties", {})
        if "recurrence" in type_props:
            rec = type_props["recurrence"]
            trig_info["schedule"] = {
                "frequency": rec.get("frequency"),
                "interval": rec.get("interval"),
            }

        pipeline_refs = props.get("pipelines", [])
        if pipeline_refs:
            trig_info["pipeline_refs"] = [
                p.get("pipelineReference", {}).get("referenceName")
                for p in pipeline_refs
            ]

        grading_info["triggers"].append(trig_info)

    for df in resources.get("dataflows", []):
        props = df.get("properties", {})
        df_info = {
            "name": df.get("name") or props.get("name", "Unknown"),
            "type": props.get("type", "Unknown"),
            "source_file": df.get("_source_file", ""),
        }

        type_props = props.get("typeProperties", {})
        sources = type_props.get("sources", [])
        sinks = type_props.get("sinks", [])
        transformations = type_props.get("transformations", [])

        if sources:
            df_info["sources"] = [s.get("name") for s in sources]
        if sinks:
            df_info["sinks"] = [s.get("name") for s in sinks]
        if transformations:
            df_info["transformations"] = [
                {"name": t.get("name"), "type": t.get("description", "")}
                for t in transformations
            ]

        grading_info["dataflows"].append(df_info)

    total_activities = sum(len(p.get("activities", [])) for p in grading_info["pipelines"])
    grading_info["summary"] = {
        "total_pipelines": len(grading_info["pipelines"]),
        "total_activities": total_activities,
        "total_datasets": len(grading_info["datasets"]),
        "total_linked_services": len(grading_info["linked_services"]),
        "total_triggers": len(grading_info["triggers"]),
        "total_dataflows": len(grading_info["dataflows"]),
    }

    return grading_info


def _get_ref_name(ref: Any) -> Optional[str]:
    """Extract referenceName from a linked service reference."""
    if isinstance(ref, dict):
        return ref.get("referenceName")
    return None


def generate_grading_report(grading_info: Dict[str, Any]) -> str:
    """Format grading information into a readable text report.

    Args:
        grading_info: Structured grading information from extract_grading_info

    Returns:
        Formatted text report with ADF project details
    """
    lines = []

    lines.append("=" * 60)
    lines.append("AZURE DATA FACTORY PROJECT REPORT")
    lines.append("=" * 60)
    lines.append("")

    lines.append("PIPELINES:")
    if grading_info.get("pipelines"):
        for p in grading_info["pipelines"]:
            lines.append(f"  - {p['name']}")
            if p.get("parameters"):
                lines.append(f"    Parameters: {', '.join(p['parameters'])}")
            if p.get("variables"):
                lines.append(f"    Variables: {', '.join(p['variables'])}")
            lines.append(f"    Activities ({len(p.get('activities', []))}):")
            for act in p.get("activities", []):
                lines.append(f"      • {act['name']} (type: {act['type']})")
                if act.get("depends_on"):
                    deps = ", ".join(
                        f"{d['activity']} [{','.join(d.get('conditions', []))}]"
                        for d in act["depends_on"]
                    )
                    lines.append(f"        depends on: {deps}")
                if act.get("inputs"):
                    lines.append(f"        inputs: {', '.join(act['inputs'])}")
                if act.get("outputs"):
                    lines.append(f"        outputs: {', '.join(act['outputs'])}")
                if act.get("source_type"):
                    lines.append(f"        source: {act['source_type']}")
                if act.get("sink_type"):
                    lines.append(f"        sink: {act['sink_type']}")
            lines.append("")
    else:
        lines.append("  none")
        lines.append("")

    lines.append("DATASETS:")
    if grading_info.get("datasets"):
        for ds in grading_info["datasets"]:
            lines.append(f"  - {ds['name']} (type: {ds['type']})")
            if ds.get("linked_service"):
                lines.append(f"    linked service: {ds['linked_service']}")
            if ds.get("table_name"):
                lines.append(f"    table: {ds['table_name']}")
            if ds.get("location_type"):
                loc_parts = [f"type: {ds['location_type']}"]
                if ds.get("folder_path"):
                    loc_parts.append(f"folder: {ds['folder_path']}")
                if ds.get("file_name"):
                    loc_parts.append(f"file: {ds['file_name']}")
                lines.append(f"    location: {', '.join(loc_parts)}")
            if ds.get("schema_columns"):
                lines.append(f"    schema ({len(ds['schema_columns'])} columns):")
                for col in ds["schema_columns"][:10]:
                    lines.append(f"      • {col['name']} ({col.get('type', '?')})")
                if len(ds["schema_columns"]) > 10:
                    lines.append(f"      ... and {len(ds['schema_columns']) - 10} more")
            lines.append("")
    else:
        lines.append("  none")
        lines.append("")

    lines.append("LINKED SERVICES:")
    if grading_info.get("linked_services"):
        for ls in grading_info["linked_services"]:
            parts = [f"{ls['name']} (type: {ls['type']})"]
            lines.append(f"  - {parts[0]}")
            if ls.get("url"):
                lines.append(f"    url: {ls['url']}")
            if ls.get("account_name"):
                lines.append(f"    account: {ls['account_name']}")
            if ls.get("database_name"):
                lines.append(f"    database: {ls['database_name']}")
    else:
        lines.append("  none")
    lines.append("")

    lines.append("TRIGGERS:")
    if grading_info.get("triggers"):
        for trig in grading_info["triggers"]:
            lines.append(f"  - {trig['name']} (type: {trig['type']})")
            if trig.get("schedule"):
                sched = trig["schedule"]
                lines.append(f"    schedule: every {sched.get('interval')} {sched.get('frequency')}")
            if trig.get("pipeline_refs"):
                lines.append(f"    pipelines: {', '.join(trig['pipeline_refs'])}")
    else:
        lines.append("  none")
    lines.append("")

    lines.append("DATAFLOWS:")
    if grading_info.get("dataflows"):
        for df in grading_info["dataflows"]:
            lines.append(f"  - {df['name']} (type: {df['type']})")
            if df.get("sources"):
                lines.append(f"    sources: {', '.join(df['sources'])}")
            if df.get("sinks"):
                lines.append(f"    sinks: {', '.join(df['sinks'])}")
            if df.get("transformations"):
                lines.append(f"    transformations: {', '.join(t['name'] for t in df['transformations'])}")
    else:
        lines.append("  none")
    lines.append("")

    lines.append("SUMMARY:")
    for k, v in (grading_info.get("summary") or {}).items():
        lines.append(f"  - {k}: {v}")

    return "\n".join(lines)


def analyze_adf(adf_path: str) -> str:
    """Complete analysis pipeline: discover, extract, and format ADF project.

    Convenience function that chains discover_adf_resources,
    extract_grading_info, and generate_grading_report.

    Args:
        adf_path: Path to the ADF project directory or single JSON file

    Returns:
        Formatted text report of the ADF project

    Raises:
        ValueError: If the path is invalid
    """
    p = Path(adf_path).resolve()

    if p.is_file() and p.suffix.lower() == ".json":
        data = parse_adf_json(str(p))
        resources = {
            "pipelines": [], "datasets": [], "linkedServices": [],
            "triggers": [], "dataflows": [],
        }
        _classify_loose_json(data, resources, p.name)
        grading_info = extract_grading_info(resources)
        return generate_grading_report(grading_info)

    if p.is_dir():
        resources = discover_adf_resources(str(p))
        grading_info = extract_grading_info(resources)
        return generate_grading_report(grading_info)

    raise ValueError(
        f"Invalid ADF path: {adf_path}\n"
        f"Expected a directory or .json file."
    )
