# -*- coding: utf-8 -*-
"""
HTTP-транспорт.
"""
import json
import sys
import tvmauth

from stocks3.core.factories import transports
from stocks3.core.transport import Transport
from stocks3.core.place import MongoPlace
from stocks3.share.curl import load_url_data, second_load_url_data
from stocks3.core.config import ConfigurationError, parse_xml, read_attrib_or_text
from datetime import datetime
from datetime import timedelta
from stocks3.share.dateandtime import str_to_timedelta
from stocks3.share import messages
from stocks3.core.utils import force_decode
from stocks3.core.config import Configuration

__author__ = "Zasimov Alexey"
__email__ = "zasimov-a@yandex-team.ru"


class AbstractHTTPTransport(Transport):
    """
    Читает только username и password.
    """

    def __init__(self, tree, node, source):
        self.auth_file = ''
        self.username = ''
        self.password = ''
        Transport.__init__(self, tree, node, source)

    def makeConfig(self):
        Transport.makeConfig(self)
        self.auth_file = self.readString("query", "authfile", '') or None
        if self.auth_file is not None:
            authfile = parse_xml(self.auth_file)
            auth = authfile.getroot()
            self.username = read_attrib_or_text(auth, "username", '') or None
            self.password = read_attrib_or_text(auth, "password", '') or None
        else:
            self.username = self.readString("query", "username", '') or None
            self.password = self.readString("query", "password", '') or None


class HTTPTransport(AbstractHTTPTransport):
    """
    Загружает файл или файлы по протоколу HTTP.
    """

    def __init__(self, tree=None, node=None, source=None, config=None):
        if config is not None:
            self.url = config['url']
            self.second_fetcher = config.get('second_fetcher', False)
            self.headers = {}
            self.remove_xml_namespace = config.get('remove_xml_namespace', False)
            self.use_tvm = False
            self.tvm_secret = None
            self.self_tvm_id = None
            self.dst_tvm_id = None
            self.source = source
            AbstractHTTPTransport.__init__(self, None, None, source)
        else:
            self.url = ''
            self.second_fetcher = False
            self.headers = {}
            self.remove_xml_namespace = ''
            self.use_tvm = False
            self.tvm_secret = None
            self.self_tvm_id = None
            self.dst_tvm_id = None
            AbstractHTTPTransport.__init__(self, tree, node, source)

    def makeConfig(self):
        AbstractHTTPTransport.makeConfig(self)
        self.url = self.readString("query", "url")
        self.second_fetcher = self.readString("query", "second_fetcher", default=False)

        self.use_tvm = self.readString("query", "use_tvm", default=False)
        if self.use_tvm:
            with open("config/tvm.secret") as tvm_secret_file:
                self.tvm_secret = tvm_secret_file.read().strip()

            config = Configuration()
            self.self_tvm_id = config.get("tvm.self_tvm_id")

            self.dst_tvm_id = self.readInt("query", "dst_tvm_id", default=0)

            tvm_client = tvmauth.TvmClient(tvmauth.TvmApiClientSettings(
                self_tvm_id=self.self_tvm_id,
                self_secret=self.tvm_secret,
                dsts={"destination": self.dst_tvm_id}
            ))
            self.headers['X-Ya-Service-Ticket'] = tvm_client.get_service_ticket_for("destination")

        if self.readString("query", "header", ''):
            self.headers[self.readString("query", "header")] = self.readString("query", "header_value")

        # Параметр сделан специально для ECB, чтобы убрать нэймспейсы
        self.remove_xml_namespace = self.readBool("query", "remove_xml_namespace", default=False)

    def transfer(self, place):
        AbstractHTTPTransport.transfer(self, place)
        headers = self.headers.items() if self.headers else None
        if self.second_fetcher:
            data = second_load_url_data(self.url, self.username, self.password, additional_headers=headers)
        else:
            data = load_url_data(self.url, self.username, self.password, additional_headers=headers)
        if self.remove_xml_namespace:
            data = data.replace(b' xmlns="', b' xmlnamespace="')
        place.store(data, self.url)


class HTTPTransportMulti(AbstractHTTPTransport):
    """
    Транспорт, которые читает данные в отдельные файлы, работая со множество
    URL-ок.
    """

    def __init__(self, tree, node, source):
        self._urls = []
        AbstractHTTPTransport.__init__(self, tree, node, source)

    def makeConfig(self):
        AbstractHTTPTransport.makeConfig(self)
        self._urls = []
        for url in self.node.findall("query/url"):
            name = url.attrib["name"]
            self._urls.append((name, url.text.strip()))

    def transfer(self, place):
        AbstractHTTPTransport.transfer(self, place)
        storage = {}
        for name, url in self._urls:
            try:
                data = load_url_data(url, self.username, self.password)
                storage[name] = force_decode(data)
            except Exception as e:
                # FIXME: очень плохо, что здесь используется messages.error
                messages.error("ERROR: %s: http_multi: %s" % (self.__class__.__name__, str(e)))
        place.store(json.dumps(storage))


class HTTPTransportWithDateTime(HTTPTransport):
    """
    Транспорт, который умеет обрабатывать URL, добавляя в него дату и время.

    В конфигурационной ветке query могут быть указаны параметры:
        - start и end, если в шаблоне URL присутствует параметр %{end}.

    В шаблоне можно использовать параметр %{today}, если необходимо вставить
    текущую дату.

    Формат даты берется из output-datetime-format, но параметры start и end не
    обрабатывается. Заметка: параметр end может содержать %{today} и тогда
    будет добавлено текущее время. Вместо параметров start и end можно
    использовать start-delta и end-delta.
    """

    DEF_INPUT_DATETIME_FORMAT = "%d.%m.%Y %H:%M:%S"

    def __init__(self, tree, node, source):
        self.output_datetime_format = ''
        self.input_datetime_format = ''
        self.today = None
        HTTPTransport.__init__(self, tree, node, source)

    def makeConfig(self):
        def to_date(ds):
            """
            Вспомогательная функция для перегонки строк в время и дату.
            """
            if ds is None:
                return None
            if ds == "today":
                return datetime.today()
            elif ds == "tomorrow":
                return datetime.today() + timedelta(days=1)
            else:
                return datetime.strptime(ds, self.input_datetime_format)

        HTTPTransport.makeConfig(self)
        self.output_datetime_format = self.readString("query", "output-datetime-format")
        self.input_datetime_format = self.readString("query", "input-datetime-format", self.DEF_INPUT_DATETIME_FORMAT)

        # Определяем start и end.
        start = self.readString("query", "start", "") or None
        end = self.readString("query", "end", "") or None
        start_delta = self.readString("query", "start-delta", "") or None
        end_delta = self.readString("query", "end-delta", "") or None

        start_date = to_date(start)
        end_date = to_date(end)

        if self.url.find("%{start}") != -1:
            if start is None:
                if end is None or start_delta is None:
                    raise ConfigurationError("Expected: start or end and start-delta.")
                start_date = end_date - str_to_timedelta(start_delta)

        if self.url.find("%{end}") != -1:
            if start is None or end_delta is None:
                raise ConfigurationError("Expected: end or start and end-delta.")
            end_date = start_date + str_to_timedelta(end_delta)

        self.today = datetime.today()

        # Обрабатываем шаблон
        self.url = self.url.replace("%{today}", self.today.strftime(self.output_datetime_format))
        tomorrow = self.today + timedelta(days=1)
        self.url = self.url.replace("%{tomorrow}", tomorrow.strftime(self.output_datetime_format))
        yesterday = self.today - timedelta(days=1)
        self.url = self.url.replace("%{yesterday}", yesterday.strftime(self.output_datetime_format))
        if start_date is not None:
            self.url = self.url.replace("%{start}", start_date.strftime(self.output_datetime_format))
        if end_date is not None:
            self.url = self.url.replace("%{end}", end_date.strftime(self.output_datetime_format))


class HTTPTransportWithDateTimeNew(HTTPTransport):
    """
    Транспорт, который умеет обрабатывать URL, добавляя в него дату и время.

    В конфигурационной ветке query могут быть указаны параметры:
        - start и end, если в шаблоне URL присутствует параметр %{end}.

    В шаблоне можно использовать параметр {today}, если необходимо вставить
    текущую дату.

    Формат даты берется из datetime-format, но параметры start и end не
    обрабатывается. Заметка: параметр end может содержать {today} и тогда
    будет добавлено текущее время. Вместо параметров start и end можно
    использовать start-delta и end-delta.
    """

    DEFAULT_DATETIME_FORMAT = "%d.%m.%Y %H:%M:%S"

    def __init__(self, tree=None, node=None, source=None, config=None):
        super().__init__(tree, node, source, config)
        if config is None:
            raise "Support only JSON sources"
        self.datetime_format = config.get('datetime-format', HTTPTransportWithDateTimeNew.DEFAULT_DATETIME_FORMAT)
        self.today = None
        self.makeConfig()

    def makeConfig(self):
        self.today = datetime.today()
        self.url = self.url.format(
            today=self.today.strftime(self.datetime_format),
            tomorrow=(self.today + timedelta(days=1)).strftime(self.datetime_format),
            yesterday=(self.today - timedelta(days=1)).strftime(self.datetime_format),
        )


class HTTPTransportPostWithDateTime(HTTPTransportWithDateTime):
    """
    Транспорт с датами и POST-запросом
    """

    def __init__(self, tree, node, source):
        self.output_datetime_format = ''
        self.input_datetime_format = ''
        self.today = None
        HTTPTransport.__init__(self, tree, node, source)

    def makeConfig(self):
        HTTPTransportWithDateTime.makeConfig(self)
        self.post = self.readString("query", "post", "") or None

        # Обрабатываем шаблон
        self.post = self.post.replace("%{today}", self.today.strftime(self.output_datetime_format))
        tomorrow = self.today + timedelta(days=1)
        self.post = self.post.replace("%{tomorrow}", tomorrow.strftime(self.output_datetime_format))
        yesterday = self.today - timedelta(days=1)
        self.post = self.post.replace("%{yesterday}", yesterday.strftime(self.output_datetime_format))

    def transfer(self, place):
        headers = self.headers.items() if self.headers else None
        if self.second_fetcher:
            data = second_load_url_data(self.url, self.username, self.password, additional_headers=headers,
                                        post_data=self.post)
        else:
            data = load_url_data(self.url, self.username, self.password, additional_headers=headers,
                                 post_data=bytes(self.post, encoding='utf-8'))
        if self.remove_xml_namespace:
            data = data.replace(b' xmlns="', b' xmlnamespace="')
        place.store(data, self.url)


# Регистрируем транспорты
transports.register("stocks3.transports.HTTP", HTTPTransport)
transports.register("stocks3.transports.HTTPWithDatetime", HTTPTransportWithDateTime)
transports.register("stocks3.transports.HTTPWithDatetimeNew", HTTPTransportWithDateTimeNew)
transports.register("stocks3.transports.HTTPPostWithDatetime", HTTPTransportPostWithDateTime)
transports.register("stocks3.transports.HTTPMulti", HTTPTransportMulti)
