from datetime import datetime, timezone
from typing import Awaitable, Callable, Iterator, Optional

from transliterate import slugify

from maps_adv.geosmb.clients.geosearch import AddressComponent, GeoSearchOrgResult
from maps_adv.geosmb.landlord.server.lib.exceptions import SlugInUse

__all__ = ["SlugMaker"]


class SlugMaker:
    __slots__ = ("_check",)

    MAX_FULL_SLUG_LENGTH: int = 63
    MAX_CROPPED_SLUG_LENGTH: int = 30

    _check: Callable[[str], Awaitable[bool]]

    def __init__(self, result_checker: Callable[[str], Awaitable[bool]]):
        self._check = result_checker

    async def __call__(
        self,
        org_info: Optional[GeoSearchOrgResult] = None,
        landing_data: Optional[dict] = None,
    ) -> str:
        if org_info is not None:
            return await self.slug_by_org_info(org_info)
        else:
            return await self.slug_by_landing_data(landing_data)

    async def slug_by_org_info(self, org_info: GeoSearchOrgResult) -> str:
        slug = self._crop_slug(self._slugify(org_info.name))

        if not await self._check(slug):
            original_slug = slug
            street = org_info.address_components.get(AddressComponent.STREET)

            if street is not None:
                slug = self._slugify(f"{original_slug}-{street}")

            if (
                street is None
                or not await self._check(slug)
                or len(slug) > self.MAX_FULL_SLUG_LENGTH
            ):
                timestamp = int(datetime.now(timezone.utc).timestamp())
                slug = self._slugify(f"{original_slug}-{timestamp}")

        return slug

    async def slug_by_landing_data(self, landing_data: dict) -> str:  # noqa: F811
        slug = self._crop_slug(self._slugify(landing_data["name"]))

        if not await self._check(slug):
            original_slug = slug
            geo = landing_data["contacts"].get("geo")
            street = None
            if geo:
                street = landing_data["contacts"]["geo"].get("street_address")

                if street is not None:
                    slug = self._slugify(f"{original_slug}-{street}")

            if (
                street is None
                or not await self._check(slug)
                or len(slug) > self.MAX_FULL_SLUG_LENGTH
            ):
                timestamp = int(datetime.now(timezone.utc).timestamp())
                slug = self._slugify(f"{original_slug}-{timestamp}")

        return slug

    async def make_slug(self, slug: str, templates: Iterator[str]) -> str:
        base_slug = self._crop_slug(self._slugify(slug))
        if await self._check(base_slug):
            return base_slug
        for template in templates:
            slug = template.format(base_slug)
            if await self._check(slug) and len(slug) < self.MAX_FULL_SLUG_LENGTH:
                return slug
        raise SlugInUse

    @staticmethod
    def _slugify(value: str) -> str:
        value = value.strip()

        slugified = slugify(value)
        if slugified is None:
            slugified = slugify(value, language_code="ru")

        return slugified.replace("_", "-").strip("-")

    def _crop_slug(self, value: str) -> str:
        if len(value) <= self.MAX_CROPPED_SLUG_LENGTH:
            return value

        words = value.split("-")
        cropped = words[0]

        for word in words[1:]:
            if len(f"{cropped}-{word}") > self.MAX_CROPPED_SLUG_LENGTH:
                break
            cropped = f"{cropped}-{word}"

        return cropped
