from datetime import datetime, timedelta, timezone
from typing import Mapping

import pytz
from yql.client.parameter_value_builder import YqlParameterValueBuilder

from maps_adv.common.yt_utils import BaseImportWithYqlTask
from maps_adv.geosmb.marksman.server.lib.data_manager import BaseDataManager
from maps_adv.geosmb.marksman.server.lib.domain import Domain

__all__ = ["CdpUsersSyncTask"]


class CdpUsersSyncTask(BaseImportWithYqlTask):
    TIMEOUT: int = 1200

    CHUNKED_WRITER_METHOD_NAME = "sync_businesses_segments"
    YQL = """
        USE {yt_cluster};

        DECLARE $biz_ids AS List<Uint64>;
        DECLARE $date_to_match_from AS String;
        DECLARE $date_to_match_to AS String;
        
        $metrika_logs_dir = "//statbox/cooked_logs/visit-cooked-log/v1/1d";
        
        $clients = (
            SELECT
                doorman_id,
                biz_id,
                email,
                phone,
                segments,
                labels
            FROM `{doorman_clients_table}`
            WHERE biz_id IN $biz_ids
                AND (email IS NOT NULL OR phone IS NOT NULL)
        );
        
        $leads = (
            SELECT
                promoter_id,
                biz_id,
                passport_uid,
                yandex_uid,
                segments
            FROM `{promoter_leads_table}`
            WHERE biz_id IN $biz_ids
                AND (passport_uid IS NOT NULL OR yandex_uid IS NOT NULL)
        );
        
        $leads_with_yandex_uid = (
            SELECT 
                leads.promoter_id AS promoter_id,
                leads.biz_id AS biz_id,
                leads.segments AS segments,
                ListSort(AGGREGATE_LIST(DISTINCT metrika.FirstPartyCookie)) AS client_ids
            FROM $leads AS leads
            JOIN (
                SELECT *
                FROM RANGE(
                    $metrika_logs_dir,
                    $date_to_match_from,
                    $date_to_match_to
                )
                WHERE UserIDType = 1
            ) AS metrika
                ON CAST(leads.yandex_uid AS Uint64) = metrika.UserID
            WHERE leads.yandex_uid IS NOT NULL
            GROUP BY leads.promoter_id, leads.biz_id, leads.segments
        );
        
        $leads_with_passport_uid = (
            SELECT 
                leads.promoter_id AS promoter_id,
                leads.biz_id AS biz_id,
                leads.segments AS segments,
                ListSort(AGGREGATE_LIST(DISTINCT metrika.FirstPartyCookie)) AS client_ids
            FROM $leads AS leads
            JOIN (
                SELECT *
                FROM RANGE(
                    $metrika_logs_dir,
                    $date_to_match_from,
                    $date_to_match_to
                )
                WHERE PassportUserID IS NOT NULL
            ) AS metrika
                ON CAST(leads.passport_uid AS Uint64) = metrika.PassportUserID
            WHERE leads.passport_uid IS NOT NULL
            GROUP BY leads.promoter_id, leads.biz_id, leads.segments
        );
        
        $leads_with_client_ids = (
            SELECT
                COALESCE(with_yandex_uid.promoter_id, with_passport_uid.promoter_id) AS promoter_id,
                COALESCE(with_yandex_uid.biz_id, with_passport_uid.biz_id) AS biz_id,
                COALESCE(with_yandex_uid.segments, with_passport_uid.segments) AS segments,
                ListSort(ListUniq(ListExtend(
                    COALESCE(with_yandex_uid.client_ids, ListCreate(Uint64)),
                    COALESCE(with_passport_uid.client_ids, ListCreate(Uint64))
                ))) AS client_ids
            FROM $leads_with_yandex_uid AS with_yandex_uid
            FULL JOIN $leads_with_passport_uid AS with_passport_uid
                USING (promoter_id)
        );
        
        $clients_for_upload = (
            SELECT
                ('doorman_' || CAST (doorman_id AS String)) AS id,
                biz_id,
                email,
                phone,
                ListCreate(Uint64) AS client_ids,
                segments,
                labels
            FROM $clients
            
            UNION ALL
            
            SELECT
                'promoter_' || CAST (promoter_id AS String) AS id,
                biz_id,
                NULL AS email,
                NULL AS phone,
                client_ids,
                segments,
                ListCreate(String) AS labels
            FROM $leads_with_client_ids
        );
        
        SELECT
            id,
            biz_id,
            email,
            phone,
            client_ids,
            segments,
            labels
        FROM $clients_for_upload
        ORDER BY biz_id, id
    """  # noqa

    def __init__(
        self, *args, config: Mapping, dm: BaseDataManager, domain: Domain, **kwargs
    ):
        super().__init__(
            yql_token=config["YQL_TOKEN"],
            yql_format_kwargs=dict(
                yt_cluster=config["YT_CLUSTER"],
                doorman_clients_table=config["DOORMAN_CLIENTS_TABLE"],
                promoter_leads_table=config["PROMOTER_LEADS_TABLE"],
            ),
            data_consumer=domain,
        )
        self.dm = dm

    async def fetch_yql_query_params(self) -> dict:
        now = datetime.now(tz=timezone.utc)
        europe_moscow_tz = pytz.timezone("Europe/Moscow")
        biz_ids = await self.dm.list_biz_ids()
        return {
            "$date_to_match_from": YqlParameterValueBuilder.make_string(
                (now - timedelta(days=6))
                .astimezone(europe_moscow_tz)
                .strftime("%Y-%m-%d")
            ),
            "$date_to_match_to": YqlParameterValueBuilder.make_string(
                (now - timedelta(days=1))
                .astimezone(europe_moscow_tz)
                .strftime("%Y-%m-%d")
            ),
            "$biz_ids": YqlParameterValueBuilder.make_list(
                map(YqlParameterValueBuilder.make_uint64, biz_ids)
            ),
        }

    def __reduce__(self):
        reduced = super().__reduce__()

        state_dict = reduced[2].copy()
        # This property is not picklable, but not needed in subprocess
        del state_dict["dm"]

        return reduced[0], reduced[1], state_dict

    @classmethod
    def _yt_row_decode(cls, row: tuple) -> dict:
        return dict(
            zip(
                ("id", "biz_id", "email", "phone", "client_ids", "segments", "labels"),
                row,
            )
        )


async def sync_segments_sizes(*args, domain: Domain, **kwargs):
    await domain.sync_segments_sizes()
