"""
init-mater-project

Copyright (C) 2025 [Lauranne Sarribouette] <lauranne.sarribouette@univ-grenoble-alpes.fr>

SPDX-License-Identifier: LGPL-3.0-or-later
"""

from typing import List, Dict, Tuple


ALLOWED_DIMENSION_NAMES = {"location", "object", "process"}


def validate_mater_ready_data(data):
    """Validate MATER-ready data structure"""
    errors = []

    # 1. Top-level structure
    if not isinstance(data, dict):
        print("❌ Data must be a dictionary")
        return False

    if "input_data" not in data or not isinstance(data["input_data"], list):
        print("❌ 'input_data' must be a list")
        return False

    if "provider" not in data or not isinstance(data["provider"], dict):
        print("❌ 'provider' must be a dictionary")
        return False

    if "metadata" not in data or not isinstance(data["metadata"], dict):
        print("❌ 'metadata' must be a dictionary")
        return False

    # Helper to validate sections with string fields
    def validate_section(section_name, section_data, required_fields):
        section_keys = set(section_data.keys())
        if section_keys != required_fields:
            missing = required_fields - section_keys
            extra = section_keys - required_fields
            if missing:
                errors.append(f"{section_name}: Missing fields: {', '.join(missing)}")
            if extra:
                errors.append(f"{section_name}: Extra fields: {', '.join(extra)}")
            return

        for field in required_fields:
            if not isinstance(section_data[field], str) or not section_data[field]:
                errors.append(f"{section_name}: '{field}' must be non-empty string")

    # 2. Validate provider and metadata
    validate_section(
        "Provider", data["provider"], {"first_name", "last_name", "email_address"}
    )
    validate_section("Metadata", data["metadata"], {"source", "link", "project"})

    # 3. Validate input_data
    REQUIRED_FIELDS = {
        "time",
        "value",
        "variable",
        "scenario",
        "unit",
        "dimensions_values",
    }

    for index, record in enumerate(data["input_data"], start=1):
        if not isinstance(record, dict):
            errors.append(f"Record {index}: Must be a dictionary")
            continue

        # Check exact fields
        record_fields = set(record.keys())
        if record_fields != REQUIRED_FIELDS:
            missing = REQUIRED_FIELDS - record_fields
            extra = record_fields - REQUIRED_FIELDS
            if missing:
                errors.append(f"Record {index}: Missing fields: {', '.join(missing)}")
            if extra:
                errors.append(f"Record {index}: Extra fields: {', '.join(extra)}")
            continue

        # Check types
        if not isinstance(record["time"], int):
            errors.append(
                f"Record {index}: 'time' must be int, got {type(record['time']).__name__}"
            )
        elif not (1000 <= record["time"] <= 3000):
            errors.append(
                f"Record {index}: 'time' out of range (1000-3000): {record['time']}"
            )

        if not isinstance(record["value"], (int, float)):
            errors.append(
                f"Record {index}: 'value' must be int or float, got {type(record['value']).__name__}"
            )

        for field in ["variable", "scenario", "unit"]:
            if not isinstance(record[field], str) or not record[field]:
                errors.append(f"Record {index}: '{field}' must be non-empty string")

        if (
            not isinstance(record["dimensions_values"], dict)
            or not record["dimensions_values"]
        ):
            errors.append(
                f"Record {index}: 'dimensions_values' must be non-empty dictionary"
            )
        else:
            for k, v in record["dimensions_values"].items():
                # Check dict keys and values are strings
                if not isinstance(k, str) or not isinstance(v, str):
                    errors.append(
                        f"Record {index}: 'dimensions_values' must have string keys and values"
                    )
                    break
                # Validate dimension names
                if k not in ALLOWED_DIMENSION_NAMES and not any(
                    k.startswith(f"{base}_") for base in ALLOWED_DIMENSION_NAMES
                ):
                    errors.append(
                        f"Record {index}: invalid dimension name '{k}'. "
                        f"Must be one of {ALLOWED_DIMENSION_NAMES} or derived (e.g., 'location_production')"
                    )

    # 4. Display results
    if errors:
        print(f"❌ {len(errors)} error(s) found:\n")
        for error in errors:
            print(f"  • {error}")
        return False

    print("✓ Validation passed\n")
    print(f"  {len(data['input_data'])} records validated")
    if data["input_data"]:
        all_variables = set()
        all_dims = set()
        for record in data["input_data"]:
            all_variables.add(record["variable"])
            all_dims.update(record["dimensions_values"].keys())
        print(f"  Variables: {', '.join(sorted(all_variables))}")
        print(f"  Dimensions: {', '.join(sorted(all_dims))}")
    return True


def validate_local_dimensions(dimensions):
    """Validate local dimensions structure"""
    errors = []

    # 1. Top-level structure
    if not isinstance(dimensions, list):
        print("❌ Dimensions must be a list")
        return False

    # 2. Validate each dimension
    REQUIRED_FIELDS = {"name", "value", "equivalence", "parents_values"}

    for index, dim in enumerate(dimensions, start=1):
        if not isinstance(dim, dict):
            errors.append(f"Dimension {index}: Must be a dictionary")
            continue

        # Check exact fields
        dim_fields = set(dim.keys())
        if dim_fields != REQUIRED_FIELDS:
            missing = REQUIRED_FIELDS - dim_fields
            extra = dim_fields - REQUIRED_FIELDS
            if missing:
                errors.append(
                    f"Dimension {index}: Missing fields: {', '.join(missing)}"
                )
            if extra:
                errors.append(f"Dimension {index}: Extra fields: {', '.join(extra)}")
            continue

        # Check types
        for field in ["name", "value"]:
            if not isinstance(dim[field], str) or not dim[field]:
                errors.append(f"Dimension {index}: '{field}' must be non-empty string")

        if "name" in dim and dim["name"] not in ALLOWED_DIMENSION_NAMES:
            errors.append(
                f"Dimension {index}: 'name' must be one of {ALLOWED_DIMENSION_NAMES}, got '{dim['name']}'"
            )

        if not isinstance(dim["equivalence"], dict):
            errors.append(f"Dimension {index}: 'equivalence' must be a dictionary")
        else:
            for k, v in dim["equivalence"].items():
                if not isinstance(k, str) or not isinstance(v, str):
                    errors.append(
                        f"Dimension {index}: 'equivalence' must have string keys and values"
                    )
                    break

        if not isinstance(dim["parents_values"], dict):
            errors.append(f"Dimension {index}: 'parents_values' must be a dictionary")
        else:
            for k, v in dim["parents_values"].items():
                if not isinstance(k, str) or not isinstance(v, str):
                    errors.append(
                        f"Dimension {index}: 'parents_values' must have string keys and values"
                    )
                    break

    # 3. Display results
    if errors:
        print(f"❌ {len(errors)} error(s) found:\n")
        for error in errors:
            print(f"  • {error}")
        return False

    print("✓ Validation passed\n")
    print(f"  {len(dimensions)} dimensions validated")
    if dimensions:
        all_dim_names = set()
        for dim in dimensions:
            all_dim_names.add(dim["name"])
        print(f"  Dimension types: {', '.join(sorted(all_dim_names))}")
    return True


def validate_mapping_completeness(mapping: List[Dict]) -> Dict:
    """
    # Validate mapping completeness by detecting TODO keys

    ## Arguments
    - `mapping` (List[Dict]): Dimensions mapping to validate

    ## Returns
    - `Dict`: Validation result with completion status and statistics
    """
    mapped_count, todo_count = count_mapping_statistics(mapping)
    total_count = len(mapping)
    completion_rate = (mapped_count / total_count * 100) if total_count > 0 else 0

    todo_entries = []
    for entry in mapping:
        equivalence = entry.get("equivalence", {})
        parents_values = entry.get("parents_values", {})

        if "TODO" in equivalence or "TODO" in parents_values:
            todo_entries.append(entry)

    return {
        "total": total_count,
        "mapped": mapped_count,
        "todo": todo_count,
        "completion_rate": completion_rate,
        "todo_entries": todo_entries,
        "can_proceed": todo_count == 0,
    }


def count_mapping_statistics(mapping: List[Dict]) -> Tuple[int, int]:
    """
    # Count mapped vs TODO entries in mapping by detecting TODO keys

    ## Arguments
    - `mapping` (List[Dict]): Mapping data

    ## Returns
    - `Tuple[int, int]`: (mapped_count, todo_count)
    """
    mapped_count = 0
    todo_count = 0

    for entry in mapping:
        equivalence = entry.get("equivalence", {})
        parents_values = entry.get("parents_values", {})

        has_ref_todo = "TODO" in equivalence
        has_parent_todo = "TODO" in parents_values

        if has_ref_todo or has_parent_todo:
            todo_count += 1
        else:
            if equivalence or parents_values:
                mapped_count += 1

    return mapped_count, todo_count
