import json
import pathlib
import random
from collections import defaultdict
from typing import Union

import click
import toml


# def chunks(data, n):
#     return [data[x : x + n] for x in range(0, len(data), n)]


def re_auth(config, instances):
    click.echo("Re-authenticating")
    for ipa in instances:
        ipa.logout()
        ipa.login(config["ipa"]["username"], config["ipa"]["password"])


class ObjectManager:
    def __init__(self, config, ipa_instances, fas_instances):
        self.config = config
        self.ipa_instances = ipa_instances
        self.fas_instances = fas_instances

    @property
    def ipa(self):
        return random.choice(self.ipa_instances)

    def check_reauth(self, counter):
        if counter % self.config["ipa"]["reauth_every"] == 0:
            re_auth(self.config, self.ipa_instances)

    def chunks(self, items):
        size = self.config["chunks"]
        return [items[x : x + size] for x in range(0, len(items), size)]


def load_data(fpath: Union[str, pathlib.Path]) -> dict:
    """Load dictionary data from a JSON, YAML, or TOML file.

    The file format will be determined from the extension of the file name.

    :param fpath:   The file path from which to load.

    :return:        The loaded data as a dictionary.
    """
    if not isinstance(fpath, pathlib.Path):
        fpath = pathlib.Path(fpath)

    suffix = fpath.suffix.lower()

    if suffix == ".toml":
        data = toml.loads(fpath.read_text())
    elif suffix == ".yaml":
        import yaml

        with fpath.open("r") as fobj:
            data = yaml.safe_load(fobj)
    else:
        data = json.loads(fpath.read_text())

    return data


class CustomJSONEncoder(json.JSONEncoder):
    """JSON encoder which serializes sets as lists"""

    def default(self, obj):
        if isinstance(obj, set):
            return list(obj)
        return super().default(obj)


def save_data(
    data: dict, fpath: Union[str, pathlib.Path], force_overwrite: bool = False
):
    """Save a dictionary object to a JSON, YAML, or TOML file.

    The file format will be determined from the extension of the file name.

    :param data:            The data to be saved.
    :param fpath:           The file path to be saved into.
    :param force_overwrite: Whether an existing file should be overwritten.
    """
    if not isinstance(fpath, pathlib.Path):
        fpath = pathlib.Path(fpath)

    suffix = fpath.suffix.lower()

    if force_overwrite:
        mode = "w"
    else:
        mode = "x"

    with fpath.open(mode) as fobj:
        if suffix == ".toml":
            toml.dump(data, fobj)
        elif suffix == ".yaml":
            import yaml

            yaml.add_representer(set, yaml.representer.SafeRepresenter.represent_list)
            yaml.add_representer(
                defaultdict, yaml.representer.SafeRepresenter.represent_dict
            )
            yaml.dump(data, fobj)
        else:
            json.dump(data, fobj, indent=2, cls=CustomJSONEncoder)


def report_conflicts(conflicts):
    if not conflicts and not any((conflicts.get("users"), conflicts.get("groups"))):
        click.echo("No users or groups with conflicts found.")

    users_to_conflicts = conflicts.get("users")

    if users_to_conflicts:
        click.echo("User conflicts")
        click.echo("==============")

        for user_name, user_conflicts in users_to_conflicts.items():
            click.echo(f"Conflicts for user '{user_name}':")

            for key, details in user_conflicts.items():
                if key == "circular_email":
                    click.echo("\tCircular email address:")
                    for item in details:
                        click.echo(f"\t\t{item['fas_name']}: {item['email_address']}")
                elif key == "email_pointing_to_other_fas":
                    click.echo("\tEmail address points to other FAS:")
                    for item in details:
                        click.echo(
                            f"\t\tEmail address {item['email_address']} for"
                            f" {', '.join(item['src_fas_names'])} points to"
                            f" {item['tgt_fas_name']}."
                        )
                elif key == "email_address_conflicts":
                    click.echo("\tConflicting email addresses between FAS instances:")
                    for item in details:
                        click.echo(
                            f"\t\t{item['email_address']}:"
                            f" {', '.join(item['fas_names'])}"
                        )
                else:
                    raise RuntimeError(f"Unknown conflicts key: {key}")

        click.echo(f"Found {len(users_to_conflicts)} users with conflicts.")

    groups_to_conflicts = conflicts.get("groups")

    if groups_to_conflicts:
        click.echo("Group conflicts")
        click.echo("===============")

        for group_name, group_conflicts in groups_to_conflicts.items():
            click.echo(f"Conflicts for group '{group_name}':")

            for key, details in group_conflicts.items():
                if key == "same_group_name":
                    click.echo(
                        f"\tSame group name between: {', '.join(details['fas_names'])}"
                    )
                else:
                    raise RuntimeError(f"Unknown conflicts key: {key}")

        click.echo(f"Found {len(groups_to_conflicts)} groups with conflicts.")
