"""Translation string lookup helpers."""
import logging
from typing import Any, Dict, Iterable, Optional

from homeassistant.loader import (
    async_get_integration,
    bind_hass,
    async_get_config_flows,
)
from homeassistant.util.json import load_json
from .typing import HomeAssistantType

_LOGGER = logging.getLogger(__name__)

TRANSLATION_STRING_CACHE = "translation_string_cache"


def recursive_flatten(prefix: Any, data: Dict) -> Dict[str, Any]:
    """Return a flattened representation of dict data."""
    output = {}
    for key, value in data.items():
        if isinstance(value, dict):
            output.update(recursive_flatten("{}{}.".format(prefix, key), value))
        else:
            output["{}{}".format(prefix, key)] = value
    return output


def flatten(data: Dict) -> Dict[str, Any]:
    """Return a flattened representation of dict data."""
    return recursive_flatten("", data)


async def component_translation_file(
    hass: HomeAssistantType, component: str, language: str
) -> Optional[str]:
    """Return the translation json file location for a component.

    For component:
     - components/hue/.translations/nl.json

    For platform:
     - components/hue/.translations/light.nl.json

    If component is just a single file, will return None.
    """
    parts = component.split(".")
    domain = parts[-1]
    is_platform = len(parts) == 2

    integration = await async_get_integration(hass, domain)
    assert integration is not None, domain

    if is_platform:
        filename = "{}.{}.json".format(parts[0], language)
        return str(integration.file_path / ".translations" / filename)

    # If it's a component that is just one file, we don't support translations
    # Example custom_components/my_component.py
    if integration.file_path.name != domain:
        return None

    filename = "{}.json".format(language)
    return str(integration.file_path / ".translations" / filename)


def load_translations_files(
    translation_files: Dict[str, str]
) -> Dict[str, Dict[str, Any]]:
    """Load and parse translation.json files."""
    loaded = {}
    for component, translation_file in translation_files.items():
        loaded_json = load_json(translation_file)
        assert isinstance(loaded_json, dict)
        loaded[component] = loaded_json

    return loaded


def build_resources(
    translation_cache: Dict[str, Dict[str, Any]], components: Iterable[str]
) -> Dict[str, Dict[str, Any]]:
    """Build the resources response for the given components."""
    # Build response
    resources = {}  # type: Dict[str, Dict[str, Any]]
    for component in components:
        if "." not in component:
            domain = component
        else:
            domain = component.split(".", 1)[0]

        if domain not in resources:
            resources[domain] = {}

        # Add the translations for this component to the domain resources.
        # Since clients cannot determine which platform an entity belongs to,
        # all translations for a domain will be returned together.
        resources[domain].update(translation_cache[component])

    return resources


@bind_hass
async def async_get_component_resources(
    hass: HomeAssistantType, language: str
) -> Dict[str, Any]:
    """Return translation resources for all components."""
    if TRANSLATION_STRING_CACHE not in hass.data:
        hass.data[TRANSLATION_STRING_CACHE] = {}
    if language not in hass.data[TRANSLATION_STRING_CACHE]:
        hass.data[TRANSLATION_STRING_CACHE][language] = {}
    translation_cache = hass.data[TRANSLATION_STRING_CACHE][language]

    # Get the set of components
    components = hass.config.components | await async_get_config_flows(hass)

    # Calculate the missing components
    missing_components = components - set(translation_cache)
    missing_files = {}
    for component in missing_components:
        path = await component_translation_file(hass, component, language)
        # No translation available
        if path is None:
            translation_cache[component] = {}
        else:
            missing_files[component] = path

    # Load missing files
    if missing_files:
        load_translations_job = hass.async_add_job(
            load_translations_files, missing_files
        )
        assert load_translations_job is not None
        loaded_translations = await load_translations_job

        # Update cache
        translation_cache.update(loaded_translations)

    resources = build_resources(translation_cache, components)

    # Return the component translations resources under the 'component'
    # translation namespace
    return flatten({"component": resources})


@bind_hass
async def async_get_translations(
    hass: HomeAssistantType, language: str
) -> Dict[str, Any]:
    """Return all backend translations."""
    resources = await async_get_component_resources(hass, language)
    if language != "en":
        # Fetch the English resources, as a fallback for missing keys
        base_resources = await async_get_component_resources(hass, "en")
        resources = {**base_resources, **resources}

    return resources
