import typing as tp

from nile.api.v1 import (
    aggregators as na,
    datetime as nd,
    filters as nf,
    stream as nstream,
)
from qb2.api.v1 import (
    filters as qf,
)

from .common import Window, make_datetime
from .constants import (
    MEMORY_LIMIT,
    ALL_DIMENSIONS,
    USERS_METRICS_DIMENSIONS,
    REJECT_REASONS,
)


def action_in_window(action_at: str, window_start: str, window_end: str) -> bool:
    return Window(
        nd.Datetime.from_iso(window_start),
        nd.Datetime.from_iso(window_end)
    ).includes(make_datetime(action_at))


def created_feedback_filter():
    return nf.custom(action_in_window, "created_at", "window_start", "window_end")


def resolved_feedback_filter():
    return nf.custom(action_in_window, "resolved_at", "window_start", "window_end")


def published_feedback_filter():
    return nf.custom(action_in_window, "published_at", "window_start", "window_end")


def need_info_feedback_filter():
    return nf.custom(action_in_window, "need_info_at", "window_start", "window_end")


def un_need_info_feedback_filter():
    return nf.custom(action_in_window, "un_need_info_at", "window_start", "window_end")


def resolved_resolution_filter():
    return qf.one_of("status", {"published", "accepted"})


def rejected_resolution_filter():
    return nf.equals("status", "rejected")


def resolved_accepted_feedback_filter():
    return nf.and_(
        resolved_resolution_filter(),
        resolved_feedback_filter())


def resolved_rejected_feedback_filter():
    return nf.and_(
        rejected_resolution_filter(),
        resolved_feedback_filter())


def resolved_with_commit_filter():
    return nf.and_(
        resolved_resolution_filter(),
        resolved_feedback_filter(),
        qf.nonzero("commit_ids"))


def reject_reason_filter(reject_reason):
    return nf.and_(
        resolved_feedback_filter(),
        rejected_resolution_filter(),
        nf.equals("reject_reason", reject_reason))


def unknown_reject_reason_filter():
    return nf.and_(
        resolved_feedback_filter(),
        rejected_resolution_filter(),
        qf.defined("reject_reason"),
        nf.not_(qf.one_of("reject_reason", REJECT_REASONS)))


def calculate_users_metrics(feedback_table: nstream.Stream) -> nstream.Stream:
    return feedback_table.groupby(
        *USERS_METRICS_DIMENSIONS,
    ).aggregate(
        users_created=na.count_distinct("some_user_id", predicate=created_feedback_filter()),
        users_resolved=na.count_distinct("some_user_id", predicate=resolved_feedback_filter()),
        users_resolved_accepted=na.count_distinct("some_user_id", predicate=resolved_accepted_feedback_filter()),
        users_resolved_with_commit=na.count_distinct("some_user_id", predicate=resolved_with_commit_filter()),

        memory_limit=MEMORY_LIMIT,
    )


def calculate_feedback_metrics(
        feedback_table: nstream.Stream,
        dimensions: tp.Optional[tp.List[str]] = None) -> nstream.Stream:
    reject_reason_metrics: tp.Dict[str, tp.Optionals[str]] = {
        f"reject_reason_{reject_reason.replace('-', '_')}": na.count(reject_reason_filter(reject_reason))
        for reject_reason in REJECT_REASONS
    }
    reject_reason_metrics.update({
        "reject_no_reason": na.count(reject_reason_filter(None)),
        "reject_unknown_reason": na.count(unknown_reject_reason_filter()),
    })

    if dimensions is None:
        dimensions = ALL_DIMENSIONS

    return feedback_table.groupby(
        *dimensions,
    ).aggregate(
        created=na.count(created_feedback_filter()),
        resolved=na.count(resolved_feedback_filter()),
        published=na.count(published_feedback_filter()),
        need_info=na.count(need_info_feedback_filter()),
        un_need_info=na.count(un_need_info_feedback_filter()),
        resolved_accepted=na.count(resolved_accepted_feedback_filter()),
        resolved_rejected=na.count(resolved_rejected_feedback_filter()),
        resolved_with_commit=na.count(resolved_with_commit_filter()),

        **reject_reason_metrics,
        memory_limit=MEMORY_LIMIT,
    )
