# -*- coding: utf-8 -*-
"""
A Translation module.

You can translate text using this module.
"""

import contextlib, random, re

import httpx

from granslate import urls, utils
from granslate.constants import (
    DEFAULT_CLIENT_SERVICE_URLS,
    DEFAULT_RAISE_EXCEPTION,
    DEFAULT_USER_AGENT,
    DUMMY_DATA,
    LANGCODES,
    LANGUAGES,
    SPECIAL_CASES,
)
from granslate.gtoken import TokenAcquirer
from granslate.models import Detected, Translated

EXCLUDES = ("en", "ca", "fr")


class Translator(object):
    """Google Translate ajax API implementation class

    You have to create an instance of Translator to use this API

    :param service_urls: google translate url list. URLs will be used randomly.
                         For example ``['translate.google.com', 'translate.google.co.kr']``
                         To preferably use the non webapp api, service url should be translate.googleapis.com
    :type service_urls: a sequence of strings

    :param user_agent: the User-Agent header to send when making requests.
    :type user_agent: :class:`str`

    :param proxies: proxies configuration.
                    Dictionary mapping protocol or protocol and host to the URL of the proxy
                    For example ``{'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}``
    :type proxies: dictionary

    :param timeout: Definition of timeout for httpx library.
                    Will be used for every request.
    :type timeout: number or a double of numbers
    :param proxies: proxies configuration.
                    Dictionary mapping protocol or protocol and host to the URL of the proxy
                    For example ``{'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}``
    :param raise_exception: if `True` then raise exception if smth will go wrong
    :type raise_exception: boolean
    """

    def __init__(
        self,
        service_urls=DEFAULT_CLIENT_SERVICE_URLS,
        user_agent=DEFAULT_USER_AGENT,
        raise_exception=DEFAULT_RAISE_EXCEPTION,
        proxy=None,
        timeout=None,
        http2=True,
    ):

        self.client = httpx.AsyncClient(http2=http2)
        if proxy is not None:  # pragma: nocover
            self.client.proxy = proxy

        self.headers = {
            "User-Agent": user_agent,
        }

        if timeout is not None:
            self.client.timeout = timeout

        self.client_type = "webapp"
        if service_urls is not None:
            self.service_urls = service_urls
            self.token_acquirer = TokenAcquirer(
                client=self.client, host=self.service_urls[0]
            )

            # if we have a service url pointing to client api we force the use of it as defaut client
            for _ in enumerate(service_urls):
                if api_type := re.search("googleapis", service_urls[0]):
                    self.service_urls = ["translate.googleapis.com"]
                    self.client_type = "gtx"
                    break
        else:
            self.service_urls = ["translate.google.com"]
            self.token_acquirer = TokenAcquirer(
                client=self.client, host=self.service_urls[0]
            )

        self.raise_exception = raise_exception

    def _pick_service_url(self):
        if len(self.service_urls) == 1:
            return self.service_urls[0]
        return random.choice(self.service_urls)

    async def _translate(self, text, dest, src, override):
        token = "xxxx"  # dummy default value here as it is not used by api client
        if self.client_type == "webapp":
            token = await self.token_acquirer.do(text)

        params = utils.build_params(
            client=self.client_type,
            query=text,
            src=src,
            dest=dest,
            token=token,
            override=override,
        )
        url = urls.TRANSLATE.format(host=self._pick_service_url())
        r = await self.client.get(url, params=params)

        if r.status_code == 200:
            data = utils.format_json(r.text)
            return data, r

        if self.raise_exception:
            raise ValueError(
                f'Unexpected status code "{r.status_code}" from {self.service_urls}'
            )

        DUMMY_DATA[0][0][0] = text
        return DUMMY_DATA, r

    def _parse_extra_data(self, data):
        response_parts_name_mapping = {
            0: "translation",
            1: "all-translations",
            2: "original-language",
            5: "possible-translations",
            6: "confidence",
            7: "possible-mistakes",
            8: "language",
            11: "synonyms",
            12: "definitions",
            13: "examples",
            14: "see-also",
        }

        return {
            category: data[index] if (index < len(data) and data[index]) else None
            for index, category in response_parts_name_mapping.items()
        }

    async def translate(self, text, dest="en", src="auto", **kwargs):
        """Translate text from source language to destination language

        :param text: The source text(s) to be translated. Batch translation is supported via sequence input.
        :type text: UTF-8 :class:`str`; :class:`unicode`; string sequence (list, tuple, iterator, generator)

        :param dest: The language to translate the source text into.
                     The value should be one of the language codes listed in :const:`googletrans.LANGUAGES`
                     or one of the language names listed in :const:`googletrans.LANGCODES`.
        :param dest: :class:`str`; :class:`unicode`

        :param src: The language of the source text.
                    The value should be one of the language codes listed in :const:`googletrans.LANGUAGES`
                    or one of the language names listed in :const:`googletrans.LANGCODES`.
                    If a language is not specified,
                    the system will attempt to identify the source language automatically.
        :param src: :class:`str`; :class:`unicode`

        :rtype: Translated
        :rtype: :class:`list` (when a list is passed)

        Basic usage:
            >>> from granslate import Translator
            >>> translator = Translator()
            >>> await translator.translate('안녕하세요.')
            <Translated src=ko dest=en text=Good evening. pronunciation=Good evening.>
            >>> await translator.translate('안녕하세요.', dest='ja')
            <Translated src=ko dest=ja text=こんにちは。 pronunciation=Kon'nichiwa.>
            >>> await translator.translate('veritas lux mea', src='la')
            <Translated src=la dest=en text=The truth is my light pronunciation=The truth is my light>

        Advanced usage:
            >>> translations = translator.translate(['The quick brown fox', 'jumps over', 'the lazy dog'], dest='ko')
            >>> for translation in translations:
            ...    print(translation.origin, ' -> ', translation.text)
            The quick brown fox  ->  빠른 갈색 여우
            jumps over  ->  이상 점프
            the lazy dog  ->  게으른 개
        """
        dest = dest.lower().split("_", 1)[0]
        src = src.lower().split("_", 1)[0]

        if src != "auto" and src not in LANGUAGES:
            if src in SPECIAL_CASES:
                src = SPECIAL_CASES[src]
            elif src in LANGCODES:
                src = LANGCODES[src]
            else:
                raise ValueError("invalid source language")

        if dest not in LANGUAGES:
            if dest in SPECIAL_CASES:
                dest = SPECIAL_CASES[dest]
            elif dest in LANGCODES:
                dest = LANGCODES[dest]
            else:
                raise ValueError("invalid destination language")

        if isinstance(text, list):
            result = []
            for item in text:
                translated = await self.translate(item, dest=dest, src=src, **kwargs)
                result.append(translated)
            return result

        origin = text
        data, response = await self._translate(text, dest, src, kwargs)

        # this code will be updated when the format is changed.
        translated = "".join([d[0] or "" for d in data[0]])

        extra_data = self._parse_extra_data(data)

        # actual source language that will be recognized by Google Translator when the
        # src passed is equal to auto.
        with contextlib.suppress(Exception):
            src = data[2]
        pron = origin
        with contextlib.suppress(Exception):
            pron = data[0][1][-2]
        if pron is None:
            with contextlib.suppress(Exception):
                pron = data[0][1][2]
        if dest in EXCLUDES and pron == origin:
            pron = translated

        return Translated(
            src=src,
            dest=dest,
            origin=origin,
            text=translated,
            pronunciation=pron,
            extra_data=extra_data,
            response=response,
        )

    async def detect(self, text, **kwargs):
        """Detect language of the input text

        :param text: The source text(s) whose language you want to identify.
                     Batch detection is supported via sequence input.
        :type text: UTF-8 :class:`str`; :class:`unicode`; string sequence (list, tuple, iterator, generator)

        :rtype: Detected
        :rtype: :class:`list` (when a list is passed)
        """
        if isinstance(text, list):
            result = []
            for item in text:
                lang = self.detect(item)
                result.append(lang)
            return result

        data, response = await self._translate(text, "en", "auto", kwargs)

        # actual source language that will be recognized by Google Translator when the
        # src passed is equal to auto.
        src = ""
        confidence = 0.0
        with contextlib.suppress(Exception):
            if len(data[8][0]) > 1:
                src = data[8][0]
                confidence = data[8][-2]
            else:
                src = "".join(data[8][0])
                confidence = data[8][-2][0]
        return Detected(lang=src, confidence=confidence, response=response)
