"""Class to describe a Growth Accounting View."""

from __future__ import annotations

from copy import deepcopy
from itertools import filterfalse
from typing import Any, Dict, Iterator, List, Optional, Union

from . import lookml_utils
from .view import View, ViewDict


class GrowthAccountingView(View):
    """A view for growth accounting measures."""

    type: str = "growth_accounting_view"
    DEFAULT_IDENTIFIER_FIELD: str = "client_id"

    other_dimensions: List[Dict[str, str]] = [
        {
            "name": "first",
            "sql": "{TABLE}.first",
            "type": "yesno",
            "hidden": "yes",
        }
    ]

    default_measures: List[Dict[str, Union[str, List[Dict[str, str]]]]] = [
        {
            "name": "overall_active_previous",
            "type": "count",
            "filters": [{"active_last_week": "yes"}],
        },
        {
            "name": "overall_active_current",
            "type": "count",
            "filters": [{"active_this_week": "yes"}],
        },
        {
            "name": "overall_resurrected",
            "type": "count",
            "filters": [
                {"new_last_week": "no"},
                {"new_this_week": "no"},
                {"active_last_week": "no"},
                {"active_this_week": "yes"},
            ],
        },
        {
            "name": "new_users",
            "type": "count",
            "filters": [{"new_this_week": "yes"}, {"active_this_week": "yes"}],
        },
        {
            "name": "established_users_returning",
            "type": "count",
            "filters": [
                {"new_last_week": "no"},
                {"new_this_week": "no"},
                {"active_last_week": "yes"},
                {"active_this_week": "yes"},
            ],
        },
        {
            "name": "new_users_returning",
            "type": "count",
            "filters": [
                {"new_last_week": "yes"},
                {"active_last_week": "yes"},
                {"active_this_week": "yes"},
            ],
        },
        {
            "name": "new_users_churned_count",
            "type": "count",
            "filters": [
                {"new_last_week": "yes"},
                {"active_last_week": "yes"},
                {"active_this_week": "no"},
            ],
        },
        {
            "name": "established_users_churned_count",
            "type": "count",
            "filters": [
                {"new_last_week": "no"},
                {"new_this_week": "no"},
                {"active_last_week": "yes"},
                {"active_this_week": "no"},
            ],
        },
        {
            "name": "new_users_churned",
            "type": "number",
            "sql": "-1 * ${new_users_churned_count}",
        },
        {
            "name": "established_users_churned",
            "type": "number",
            "sql": "-1 * ${established_users_churned_count}",
        },
        {
            "name": "overall_churned",
            "type": "number",
            "sql": "${new_users_churned} + ${established_users_churned}",
        },
        {
            "name": "overall_retention_rate",
            "type": "number",
            "sql": (
                "SAFE_DIVIDE("
                "(${established_users_returning} + ${new_users_returning}),"
                "${overall_active_previous}"
                ")"
            ),
        },
        {
            "name": "established_user_retention_rate",
            "type": "number",
            "sql": (
                "SAFE_DIVIDE("
                "${established_users_returning},"
                "(${established_users_returning} + ${established_users_churned_count})"
                ")"
            ),
        },
        {
            "name": "new_user_retention_rate",
            "type": "number",
            "sql": (
                "SAFE_DIVIDE("
                "${new_users_returning},"
                "(${new_users_returning} + ${new_users_churned_count})"
                ")"
            ),
        },
        {
            "name": "overall_churn_rate",
            "type": "number",
            "sql": (
                "SAFE_DIVIDE("
                "(${established_users_churned_count} + ${new_users_churned_count}),"
                "${overall_active_previous}"
                ")"
            ),
        },
        {
            "name": "fraction_of_active_resurrected",
            "type": "number",
            "sql": "SAFE_DIVIDE(${overall_resurrected}, ${overall_active_current})",
        },
        {
            "name": "fraction_of_active_new",
            "type": "number",
            "sql": "SAFE_DIVIDE(${new_users}, ${overall_active_current})",
        },
        {
            "name": "fraction_of_active_established_returning",
            "type": "number",
            "sql": (
                "SAFE_DIVIDE("
                "${established_users_returning},"
                "${overall_active_current}"
                ")"
            ),
        },
        {
            "name": "fraction_of_active_new_returning",
            "type": "number",
            "sql": "SAFE_DIVIDE(${new_users_returning}, ${overall_active_current})",
        },
        {
            "name": "quick_ratio",
            "type": "number",
            "sql": (
                "SAFE_DIVIDE("
                "${new_users} + ${overall_resurrected},"
                "${established_users_churned_count} + ${new_users_churned_count}"
                ")"
            ),
        },
    ]

    def __init__(
        self,
        namespace: str,
        tables: List[Dict[str, str]],
        identifier_field: str = DEFAULT_IDENTIFIER_FIELD,
    ):
        """Get an instance of a GrowthAccountingView."""
        self.identifier_field = identifier_field

        super().__init__(
            namespace, "growth_accounting", GrowthAccountingView.type, tables
        )

    @classmethod
    def get_default_dimensions(
        klass, identifier_field: str = DEFAULT_IDENTIFIER_FIELD
    ) -> List[Dict[str, str]]:
        """Get dimensions to be added to GrowthAccountingView by default."""
        return [
            {
                "name": "active_this_week",
                "sql": "mozfun.bits28.active_in_range(days_seen_bits, -6, 7)",
                "type": "yesno",
                "hidden": "yes",
            },
            {
                "name": "active_last_week",
                "sql": "mozfun.bits28.active_in_range(days_seen_bits, -13, 7)",
                "type": "yesno",
                "hidden": "yes",
            },
            {
                "name": "new_this_week",
                "sql": "DATE_DIFF(${submission_date}, first_run_date, DAY) BETWEEN 0 AND 6",
                "type": "yesno",
                "hidden": "yes",
            },
            {
                "name": "new_last_week",
                "sql": "DATE_DIFF(${submission_date}, first_run_date, DAY) BETWEEN 7 AND 13",
                "type": "yesno",
                "hidden": "yes",
            },
            {
                "name": f"{identifier_field}_day",
                "sql": f"CONCAT(CAST(${{TABLE}}.submission_date AS STRING), ${{{identifier_field}}})",
                "type": "string",
                "hidden": "yes",
                "primary_key": "yes",
            },
        ]

    @classmethod
    def from_db_views(
        klass,
        namespace: str,
        is_glean: bool,
        channels: List[Dict[str, str]],
        db_views: dict,
        identifier_field: str = DEFAULT_IDENTIFIER_FIELD,
    ) -> Iterator[GrowthAccountingView]:
        """Get Growth Accounting Views from db views and app variants."""
        dataset = next(
            (channel for channel in channels if channel.get("channel") == "release"),
            channels[0],
        )["dataset"]

        for view_id, references in db_views[dataset].items():
            if view_id == "baseline_clients_last_seen":
                yield GrowthAccountingView(
                    namespace,
                    [{"table": f"mozdata.{dataset}.{view_id}"}],
                    identifier_field=identifier_field,
                )

    @classmethod
    def from_dict(
        klass, namespace: str, name: str, _dict: ViewDict
    ) -> GrowthAccountingView:
        """Get a view from a name and dict definition."""
        return GrowthAccountingView(
            namespace,
            _dict["tables"],
            identifier_field=str(
                _dict.get(
                    "identifier_field", GrowthAccountingView.DEFAULT_IDENTIFIER_FIELD
                )
            ),
        )

    def to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]:
        """Generate LookML for this view."""
        view_defn: Dict[str, Any] = {"name": self.name}
        table = self.tables[0]["table"]

        # add dimensions and dimension groups
        dimensions = lookml_utils._generate_dimensions(table, dryrun=dryrun) + deepcopy(
            GrowthAccountingView.get_default_dimensions(
                identifier_field=self.identifier_field
            )
        )

        view_defn["dimensions"] = list(
            filterfalse(lookml_utils._is_dimension_group, dimensions)
        )
        view_defn["dimension_groups"] = list(
            filter(lookml_utils._is_dimension_group, dimensions)
        )

        # add measures
        view_defn["measures"] = self.get_measures()

        # SQL Table Name
        view_defn["sql_table_name"] = f"`{table}`"

        return {"views": [view_defn]}

    def get_measures(self) -> List[Dict[str, Union[str, List[Dict[str, str]]]]]:
        """Generate measures for the Growth Accounting Framework."""
        return deepcopy(GrowthAccountingView.default_measures)
