#!/usr/bin/env python3
"""
Module: json_transformer.py

Description:
    This module provides a utility class, `JsonTransformer`, for performing
    various transformations on JSON-like data structures (dictionaries and lists).
    It includes functionalities for filtering, renaming, and mapping keys in a JSON object.
    Error handling and logging are implemented for robustness.
"""

import json
import logging
from typing import Any, Dict, List, Optional, Union

# Configure logging
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)


class JsonTransformer:
    """
    A utility class for transforming JSON data.
    """

    def __init__(self):
        """
        Initializes the JsonTransformer.
        """
        pass

    def filter_keys(
        self, data: Union[Dict[str, Any], List[Dict[str, Any]]], keys_to_keep: List[str]
    ) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
        """
        Filters a JSON object or list of JSON objects, keeping only the specified keys.

        Args:
            data (Union[Dict[str, Any], List[Dict[str, Any]]]): The JSON object or list of JSON objects to filter.
            keys_to_keep (List[str]): A list of keys to keep in the output.

        Returns:
            Union[Dict[str, Any], List[Dict[str, Any]]]: The filtered JSON object or list of JSON objects.
            Returns an empty dict or list if an error occurs.

        Raises:
            TypeError: If the input data is not a dict or a list.
            ValueError: If the keys_to_keep list is empty.
        """
        if not isinstance(data, (dict, list)):
            logging.error(
                f"TypeError: Input data must be a dict or list, but got {type(data)}"
            )
            raise TypeError("Input data must be a dict or list")

        if not isinstance(keys_to_keep, list):
            logging.error(
                f"TypeError: keys_to_keep must be a list, but got {type(keys_to_keep)}"
            )
            raise TypeError("keys_to_keep must be a list")

        if not keys_to_keep:
            logging.warning("ValueError: keys_to_keep is empty, returning empty result.")
            if isinstance(data, dict):
                return {}
            else:
                return []
            #raise ValueError("keys_to_keep cannot be empty")

        try:
            if isinstance(data, dict):
                return {key: data[key] for key in keys_to_keep if key in data}
            elif isinstance(data, list):
                return [
                    {key: item[key] for key in keys_to_keep if key in item}
                    for item in data
                ]
            else:
                logging.error(f"Unexpected data type: {type(data)}")
                return {}  # or [] depending on expected behavior
        except KeyError as e:
            logging.error(f"KeyError: {e}. A key specified in keys_to_keep does not exist in the data.")
            return {} if isinstance(data, dict) else []
        except Exception as e:
            logging.exception(f"An unexpected error occurred: {e}")
            return {} if isinstance(data, dict) else []

    def rename_keys(
        self, data: Union[Dict[str, Any], List[Dict[str, Any]]], key_mapping: Dict[str, str]
    ) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
        """
        Renames keys in a JSON object or list of JSON objects based on the provided mapping.

        Args:
            data (Union[Dict[str, Any], List[Dict[str, Any]]]): The JSON object or list of JSON objects to transform.
            key_mapping (Dict[str, str]): A dictionary where keys are old names and values are new names.

        Returns:
            Union[Dict[str, Any], List[Dict[str, Any]]]: The transformed JSON object or list of JSON objects with renamed keys.
            Returns an empty dict or list if an error occurs.

        Raises:
            TypeError: If the input data is not a dict or a list.
            TypeError: If the key_mapping is not a dict.
        """
        if not isinstance(data, (dict, list)):
            logging.error(
                f"TypeError: Input data must be a dict or list, but got {type(data)}"
            )
            raise TypeError("Input data must be a dict or list")

        if not isinstance(key_mapping, dict):
            logging.error(
                f"TypeError: key_mapping must be a dict, but got {type(key_mapping)}"
            )
            raise TypeError("key_mapping must be a dict")

        try:
            if isinstance(data, dict):
                transformed_data = {}
                for old_key, new_key in key_mapping.items():
                    if old_key in data:
                        transformed_data[new_key] = data[old_key]
                    else:
                        logging.warning(f"Old key '{old_key}' not found in the data. Skipping.")
                # Copy any remaining keys that weren't in the mapping.
                for key, value in data.items():
                    if key not in key_mapping:
                        transformed_data[key] = value

                return transformed_data

            elif isinstance(data, list):
                transformed_data = []
                for item in data:
                    transformed_item = {}
                    for old_key, new_key in key_mapping.items():
                        if old_key in item:
                            transformed_item[new_key] = item[old_key]
                        else:
                            logging.warning(f"Old key '{old_key}' not found in the data. Skipping.")

                    # Copy any remaining keys that weren't in the mapping.
                    for key, value in item.items():
                        if key not in key_mapping:
                            transformed_item[key] = value

                    transformed_data.append(transformed_item)
                return transformed_data
            else:
                logging.error(f"Unexpected data type: {type(data)}")
                return {}  # or [] depending on expected behavior
        except Exception as e:
            logging.exception(f"An unexpected error occurred: {e}")
            return {} if isinstance(data, dict) else []

    def map_values(
        self,
        data: Union[Dict[str, Any], List[Dict[str, Any]]],
        key: str,
        value_mapping: Dict[Any, Any],
    ) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
        """
        Maps values of a specific key in a JSON object or list of JSON objects.

        Args:
            data (Union[Dict[str, Any], List[Dict[str, Any]]]): The JSON object or list of JSON objects to transform.
            key (str): The key whose values should be mapped.
            value_mapping (Dict[Any, Any]): A dictionary mapping old values to new values.

        Returns:
            Union[Dict[str, Any], List[Dict[str, Any]]]: The transformed JSON object or list of JSON objects with mapped values.
            Returns an empty dict or list if an error occurs.

        Raises:
            TypeError: If the input data is not a dict or a list.
            TypeError: If the key is not a string.
            TypeError: If the value_mapping is not a dict.
        """
        if not isinstance(data, (dict, list)):
            logging.error(
                f"TypeError: Input data must be a dict or list, but got {type(data)}"
            )
            raise TypeError("Input data must be a dict or list")

        if not isinstance(key, str):
            logging.error(f"TypeError: key must be a string, but got {type(key)}")
            raise TypeError("key must be a string")

        if not isinstance(value_mapping, dict):
            logging.error(
                f"TypeError: value_mapping must be a dict, but got {type(value_mapping)}"
            )
            raise TypeError("value_mapping must be a dict")

        try:
            if isinstance(data, dict):
                if key in data:
                    if data[key] in value_mapping:
                        data[key] = value_mapping[data[key]]
                    else:
                        logging.warning(f"Value for key '{key}' not found in value_mapping. Keeping original value.")
                return data
            elif isinstance(data, list):
                for item in data:
                    if key in item:
                        if item[key] in value_mapping:
                            item[key] = value_mapping[item[key]]
                        else:
                            logging.warning(f"Value for key '{key}' not found in value_mapping. Keeping original value.")
                return data
            else:
                logging.error(f"Unexpected data type: {type(data)}")
                return {}  # or [] depending on expected behavior
        except Exception as e:
            logging.exception(f"An unexpected error occurred: {e}")
            return {} if isinstance(data, dict) else []


if __name__ == "__main__":
    # Example Usage
    transformer = JsonTransformer()

    # Example 1: Filtering keys
    data1 = {"name": "John", "age": 30, "city": "New York"}
    keys_to_keep = ["name", "age"]
    filtered_data = transformer.filter_keys(data1, keys_to_keep)
    print(f"Filtered data: {filtered_data}")  # Output: {'name': 'John', 'age': 30}

    # Example 2: Renaming keys
    data2 = {"firstName": "John", "age": 30, "city": "New York"}
    key_mapping = {"firstName": "name"}
    renamed_data = transformer.rename_keys(data2, key_mapping)
    print(f"Renamed data: {renamed_data}")  # Output: {'name': 'John', 'age': 30, 'city': 'New York'}

    # Example 3: Mapping values
    data3 = {"name": "John", "status": "active"}
    value_mapping = {"active": True, "inactive": False}
    mapped_data = transformer.map_values(data3, "status", value_mapping)
    print(f"Mapped data: {mapped_data}")  # Output: {'name': 'John', 'status': True}

    # Example 4: List of dictionaries
    data4 = [
        {"name": "John", "age": 30, "city": "New York"},
        {"name": "Jane", "age": 25, "city": "London"},
    ]
    keys_to_keep = ["name"]
    filtered_data_list = transformer.filter_keys(data4, keys_to_keep)
    print(f"Filtered data list: {filtered_data_list}")  # Output: [{'name': 'John'}, {'name': 'Jane'}]

    # Example 5: Renaming keys in a list of dictionaries
    data5 = [
        {"firstName": "John", "age": 30, "city": "New York"},
        {"firstName": "Jane", "age": 25, "city": "London"},
    ]
    key_mapping = {"firstName": "name"}
    renamed_data_list = transformer.rename_keys(data5, key_mapping)
    print(f"Renamed data list: {renamed_data_list}")

    # Example 6: Mapping values in a list of dictionaries
    data6 = [
        {"name": "John", "status": "active"},
        {"name": "Jane", "status": "inactive"},
    ]
    value_mapping = {"active": True, "inactive": False}
    mapped_data_list = transformer.map_values(data6, "status", value_mapping)
    print(f"Mapped data list: {mapped_data_list}")