#!/usr/bin/python
# -*- coding: utf-8 -*-
import datetime
import json
import os
import re
import sys

import requests
from bs4 import BeautifulSoup

import config


class OFDProvider:
    # заводской номер фискального накопителя
    # fiscalDriveId
    # fn
    # ФН
    fiscal_drive_id = None
    # номер фискального документа
    # fiscalDocumentNumber
    # i
    # ФД
    fiscal_document_number = None
    # номер ФПД
    # фискальный признак документа (подпись)
    # fp
    # ФП
    fiscal_id = None
    # регистрационный номер ККТ
    kkt = None
    # инн
    inn = None
    # время покупки
    time = None
    # сумма чека из ОФД
    raw_sum = 0
    # подсчитанная сумма чека
    total_sum = 0
    # номер чека
    number = 0
    # идентификатор чека на сервере
    receipt_id = ""
    # данные чека
    receipt_data = None
    # опция для возможности повторной отправки данных уже сохраненного чека
    resend = False
    # место хранения и траты денег по умолчанию
    payment_method = config.payment_method['default']
    # время из чека
    raw_time = None

    # регулярное выражение для проверки соответствия формату текста QR
    ofd_type1_match_regexp = "t=([\dT]+)&s=([\d\.]+)&fn=(\d+)&i=(\d+)&fp=(\d+)&n=(\d+)"

    def __init__(self, resend):
        self.resend = resend

    def load(self, data):
        for key in data:
            setattr(self, key, data[key])

    @staticmethod
    def parse_data(fields):
        time = datetime.datetime.strptime(fields[0], "%Y%m%dT%H%M%S")
        drebtime = time.strftime("%Y-%m-%d %H:%M:%S")

        return {
            "raw_time": fields[0],
            "time": time,
            "dreb_time": drebtime,
            "raw_sum": "{0:.2f}".format(float(fields[1])),
            "fiscal_drive_id": fields[2],
            "fiscal_document_number": fields[3],
            "fiscal_id": fields[4],
            "number": fields[5]
        }

    # определение ОФД по данным чека и запросами
    def detect(self, text, kkt=None, inn=None):
        ofd_type1_match = re.match(self.ofd_type1_match_regexp, text)
        # проверка чека по обычному на данный момент содержанию QR

        if ofd_type1_match:
            # получаем данные чека
            data = self.parse_data(ofd_type1_match.groups())

            print("Ticket {3} at {0} with sum {1}, FPD {4}, fiscal drive {2} (n={5})".format(
                data['time'], data['raw_sum'], data['fiscal_drive_id'], data['fiscal_document_number'],
                data['fiscal_id'], data['number']))

            data['kkt'] = kkt
            data['inn'] = inn

            # для списка известных провайдеров
            for provider in [PlatformaOFD, Taxcom, OFDRU, OFD1, OFDYA]:
                # проверяем что данные удовлетворяют требованиям ОФД
                if provider(self.resend).is_suitable(data):
                    # инициализируем и загружаем данные
                    ofd = provider(self.resend)
                    ofd.load(data)
                    # если поиск успешен, то возвращаем инстанс чека этого ОФД
                    try:
                        if ofd.search():
                            return ofd
                    except Exception as e:
                        print("Request error: " + str(e))

            return True

        elif text.startswith("http://check.egais.ru"):
            print("This is an EGAIS receipt without sum!")
            return False
        # добавить распознавание ЕГАИС
        else:
            print("No match with known OFD in content!")
            return False

    # имя файла для сохранения контента чека из ОФД
    def get_receipt_file_name(self):
        filename = os.path.dirname(os.path.realpath(__file__)) + \
                   self.raw_time + "_" + self.fiscal_id + \
                   "_" + self.fiscal_drive_id + ".txt"
        return os.path.join(config.receipt_dir, filename)

    # имя файла для сохранения файла загрузки в Дребеденьги
    def get_csv_file_name(self):
        filename = self.raw_time + "_" + self.fiscal_id + \
                   "_" + self.fiscal_drive_id + ".csv"
        return os.path.join(config.report_dir, filename)


class OFDRU(OFDProvider):
    url_receipt_get = "https://ofd.ru/api/rawdoc/RecipeInfo?Fn={}&Kkt={}&Inn={}&Num={}&Sign={}"

    @staticmethod
    def is_suitable(data):
        return data['fiscal_drive_id'] and data['fiscal_id'] and data['fiscal_document_number'] and data['kkt'] and \
               data['inn']

    def search(self):
        print("Search in OFD.RU...")
        url = self.url_receipt_get.format(
            self.fiscal_drive_id, self.kkt, self.inn, self.fiscal_document_number, self.fiscal_id)
        request = requests.get(url)
        if request.status_code == 404:
            print("Not found!")
            return False
        else:
            self.receipt_data = request.content
            filename = self.get_receipt_file_name()

            if not os.path.exists(filename):
                with open(filename, 'w') as outfile:
                    outfile.write(self.receipt_data)
            return True

    def get_items(self):
        if self.receipt_data:
            self.total_sum = 0
            self.receipt_data = json.loads(self.receipt_data)
            items_count = len(self.receipt_data["Document"]["Items"])
            print("Found items: {}".format(items_count))

            items = []
            for item in self.receipt_data["Document"]["Items"]:
                name = item["Name"].encode('utf8')
                summa = float(item["Total"]) / 100.0
                price = float(item["Price"]) / 100.0
                count = item["Quantity"]
                self.total_sum += summa

                if count != 1:
                    items.append(
                        ("{} ({} * {})".format(name, price, count),
                         "-{0:.2f}".format(summa)))
                else:
                    items.append((name, "-{0:.2f}".format(summa)))

            print("Items total sum: {}".format(self.total_sum))
            self.total_sum = "{0:.2f}".format(self.total_sum)
            if self.total_sum != self.raw_sum:
                print("WARNING! Manually calculated sum {} is not equal to the receipt sum {}!".format(
                    self.total_sum, self.raw_sum))

            self.items = items
            return items
        else:
            print("No receipt data!")
            return False


class Taxcom(OFDProvider):
    url_receipt_get = "https://receipt.taxcom.ru/v01/show?fp={}&s={}"

    @staticmethod
    def is_suitable(data):
        return data['fiscal_id'] and not data['kkt']

    def search(self):
        print("Search in Taxcom...")
        request = requests.get(self.url_receipt_get.format(
            self.fiscal_id, self.raw_sum))
        if "Такой чек не найден" in request.content:
            print("Not found!")
            return False
        else:
            self.receipt_data = request.content
            filename = self.get_receipt_file_name()

            if not os.path.exists(filename):
                with open(filename, 'w') as outfile:
                    outfile.write(self.receipt_data)
            return True

    def get_items(self):
        if self.receipt_data:
            soup = BeautifulSoup(self.receipt_data, "lxml")
            rows = soup.select("td.position")[:-1]
            price_counts = soup.select("tr.result")
            self.total_sum = 0

            def extract_count(row_obj):
                return row_obj.find_all('span')[0].get_text().encode("utf-8")

            def extract_price(row_obj):
                return row_obj.find_all('span')[1].get_text().encode("utf-8")

            items = []
            for i, row in enumerate(rows):

                name = row.get_text().encode("utf-8")

                price = float(extract_price(price_counts[i]).replace(',', '.'))
                count = float(extract_count(price_counts[i]).replace(',', '.'))
                summa = price * count
                self.total_sum += summa
                if count != 1:
                    items.append(
                        ("{} ({} * {})".format(name, price, count),
                         "-{0:.2f}".format(summa)))
                else:
                    items.append((name, "-{0:.2f}".format(summa)))

            print("Items total sum: {}".format(self.total_sum))
            self.total_sum = "{0:.2f}".format(self.total_sum)
            if self.total_sum != self.raw_sum:
                print("WARNING! Manually calculated sum {} is not equal to the receipt sum {}!".format(
                    self.total_sum, self.raw_sum))

            self.items = items
            return items
        else:
            print("No receipt data!")
            return False


class PlatformaOFD(OFDProvider):
    url_receipt_get = "https://lk.platformaofd.ru/web/noauth/cheque?fn={}&fp={}"

    @staticmethod
    def is_suitable(data):
        return data['fiscal_drive_id'] and data['fiscal_id'] and not data['kkt']

    def search(self):
        print("Search in Platforma OFD...")
        request = requests.get(self.url_receipt_get.format(
            self.fiscal_drive_id, self.fiscal_id), verify=False)
        # ssl verification disabled
        # workaround for error: hostname 'lk.platformaofd.ru' doesn't match either of '*.evotor.ru', 'evotor.ru'
        if "Чек не найден" in request.content:
            print("Not found!")
            return False
        else:
            self.receipt_data = request.content
            filename = self.get_receipt_file_name()

            if not os.path.exists(filename):
                with open(filename, 'w') as outfile:
                    outfile.write(self.receipt_data)
            return True

    def get_items(self):
        if self.receipt_data:
            soup = BeautifulSoup(self.receipt_data, "lxml")
            rows = soup.select("div.row")
            self.total_sum = 0

            def extract_value(row_obj):
                return row_obj.find('div', {'class': 'col-xs-4'}).get_text().encode("utf-8")

            def extract_key(row_obj):
                return row_obj.find('div', {'class': 'col-xs-8'}).get_text().encode("utf-8")

            items = []
            for i, row in enumerate(rows):
                if row.get_text().encode("utf-8") != "наименование товара (реквизиты)":
                    continue
                name = extract_value(rows[i + 1])
                if extract_key(rows[i + 2]) == "штриховой код EAN13":
                    i += 1
                price = float(extract_value(rows[i + 2]))
                count = int(float(extract_value(rows[i + 3])))
                summa = float(extract_value(rows[i + 4]))
                self.total_sum += summa
                if count != 1:
                    items.append(
                        ("{} ({} * {})".format(name, price, count),
                         "-{0:.2f}".format(summa)))
                else:
                    items.append((name, "-{0:.2f}".format(summa)))

            print("Items total sum: {}".format(self.total_sum))
            self.total_sum = "{0:.2f}".format(self.total_sum)
            if self.total_sum != self.raw_sum:
                print("WARNING! Manually calculated sum {} is not equal to the receipt sum {}!".format(
                    self.total_sum, self.raw_sum))

            self.items = items
            return items
        else:
            print("No receipt data!")
            return False


class OFD1(OFDProvider):
    url_first_get = "https://consumer.1-ofd.ru/#/landing"
    url_receipt_get = "https://consumer.1-ofd.ru/api/tickets/ticket/{}"
    url_receipt_find = "https://consumer.1-ofd.ru/api/tickets/find-ticket"

    @staticmethod
    def is_suitable(data):
        return data['fiscal_drive_id'] and data['fiscal_id'] and data['fiscal_document_number'] and not data['kkt']

    def search(self):
        print("Search in ofd1...")

        ofd1_payload = {
            "fiscalDocumentNumber": self.fiscal_document_number,
            "fiscalDriveId": self.fiscal_drive_id,
            "fiscalId": self.fiscal_id
        }
        # fix for single quotes server error
        ofd1_payload = json.dumps(ofd1_payload, sort_keys=True)

        session = requests.Session()
        session.get(self.url_first_get)

        session.headers.update({
            'Content-Type': 'application/json',  # fix 415 error
            'X-XSRF-TOKEN': session.cookies.get_dict()['XSRF-TOKEN'],
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
        })

        # print(session.headers)
        # print(session.cookies.get_dict())
        # cookies = session.cookies.get_dict().copy()
        # cookies.update({
        # 	'PLAY_LANG': 'ru'
        # })
        # print(cookies)

        ofd1 = session.post(self.url_receipt_find, data=ofd1_payload)

        if ofd1.status_code == 200:
            answer = ofd1.json()
            status = answer["status"]
            self.receipt_id = answer["uid"]

            print("Getting the receipt...")
            ofd1 = requests.get(self.url_receipt_get.format(self.receipt_id))

            if ofd1.status_code == 200:
                self.raw = json.dumps(
                    ofd1.json(), ensure_ascii=False).encode('utf8')
                self.receipt_data = json.loads(self.raw)

                filename = self.get_receipt_file_name()

                if not os.path.exists(filename):
                    with open(filename, 'w') as outfile:
                        outfile.write(self.raw)
                else:
                    print("Receipt already saved!")
                    if not self.resend:
                        print("Skipping...")
                        return False

                return True
            else:
                print("Error {} while getting receipt from ofd1!".format(
                    ofd1.status_code))
                if config.debug:
                    print(ofd1.text)

        elif ofd1.status_code == 404:
            print("Not found!")

        else:
            print("Error {} while searching in ofd1!".format(ofd1.status_code))
            if config.debug:
                print(ofd1.text)

        return False

    def get_items(self):
        if self.receipt_data:
            self.total_sum = 0
            items_count = len(self.receipt_data["ticket"]["items"])
            print("Found items: {}".format(items_count))

            items = []
            for item in self.receipt_data["ticket"]["items"]:
                data = item["commodity"]
                name = data["name"].encode('utf8')
                summa = float(data["sum"])
                price = data["price"]
                count = data["quantity"]
                self.total_sum += summa

                if count != 1:
                    items.append(
                        ("{} ({} * {})".format(name, price, count),
                         "-{0:.2f}".format(summa)))
                else:
                    items.append((name, "-{0:.2f}".format(summa)))

            print("Items total sum: {}".format(self.total_sum))
            self.total_sum = "{0:.2f}".format(self.total_sum)
            if self.total_sum != self.raw_sum:
                print("WARNING! Manually calculated sum {} is not equal to the receipt sum {}!".format(
                    self.total_sum, self.raw_sum))

            self.items = items
            return items
        else:
            print("No receipt data!")
            return False


class OFDYA(OFDProvider):
    url_receipt_get = "https://ofd-ya.ru/getFiscalDoc?kktRegId={}&fiscalSign={}&json=true"

    @staticmethod
    def is_suitable(data):
        return data['fiscal_document_number'] and data['kkt']

    def search(self):
        print("Search in OFD-YA...")
        url = self.url_receipt_get.format(self.kkt, self.fiscal_id)
        request = requests.get(url)
        if request.status_code == 200 and request.text != '{}':
            self.raw = json.dumps(
                request.json(), ensure_ascii=False).encode('utf8')
            self.receipt_data = json.loads(self.raw)
            filename = self.get_receipt_file_name()

            if not os.path.exists(filename):
                with open(filename, 'w') as outfile:
                    outfile.write(self.receipt_data)

            return True
        else:
            print("Error {} while searching in ofd-ya!".format(request.status_code))
            if config.debug:
                print(request.text)
            return False

    def get_items(self):
        if self.receipt_data:
            self.total_sum = 0
            items_count = len(self.receipt_data["requestmessage"]["items"])
            print("Found items: {}".format(items_count))

            items = []
            for item in self.receipt_data["requestmessage"]["items"]:
                name = item["name"].encode('utf8')
                summa = int(item["sum"]) / 100.0
                price = int(item["price"]) / 100.0
                count = item["quantity"]
                self.total_sum += summa

                if count != 1:
                    items.append(
                        ("{} ({} * {})".format(name, price, count),
                         "-{0:.2f}".format(summa)))
                else:
                    items.append((name, "-{0:.2f}".format(summa)))

            print("Items total sum: {}".format(self.total_sum))
            self.total_sum = "{0:.2f}".format(self.total_sum)
            if self.total_sum != self.raw_sum:
                print("WARNING! Manually calculated sum {} is not equal to the receipt sum {}!".format(
                    self.total_sum, self.raw_sum))

            self.items = items
            return items
        else:
            print("No receipt data!")
            return False