# TODO адаптировать под работу с базой данных вместо локального словаря

from arka.settings import PHONE_VERIFICATION_ENABLE, PHONE_VERIFICATION_ATTEMPTS,\
    PHONE_VERIFICATION_ACCESS_KEY, PHONE_VERIFICATION_RESEND_TIME_SECS

from threading import Thread, Lock, Event
import random
import traceback
from datetime import datetime
import requests


class PhoneVerificationService:
    __lock = Lock()
    __event = Event()

    # номера, для которых отправлен запрос верификации (на внешний ресурс)
    # имеет структуру:
    # "{phone}": {"code": None|int|"FAILED", "attempts": int}
    # None в code означает что ответ еще не пришел, остальное вроде понятно
    # attempts - сколько попыток верификации осталось (по умолчанию 5)
    __codes = {}

    # очередь номеров, которые требуют верификации
    __to_verify = []
    __instance = None

    @staticmethod
    def __service_run():
        while True:
            try:
                PhoneVerificationService.__event.wait()
                phones = None
                with PhoneVerificationService.__lock:
                    if PhoneVerificationService.__event.is_set():
                        PhoneVerificationService.__event.clear()
                        phones = PhoneVerificationService.__to_verify.copy()
                        PhoneVerificationService.__to_verify.clear()

                if phones is not None:
                    for phone in phones:
                        # тут должна быть проверка, есть ли телефон в списке кодов

                        obj = {
                            "code": None,
                            "attempts": PHONE_VERIFICATION_ATTEMPTS,
                            "time": datetime.now()
                        }
                        with PhoneVerificationService.__lock:
                            PhoneVerificationService.__codes[phone] = obj
                        request_success = False

                        if PHONE_VERIFICATION_ENABLE:
                            try:
                                # параметры для sms

                                params = {
                                    "phone": lambda: phone[1:] if phone.startswith("+") else phone,
                                    "ip": -1,
                                    "api_id": PHONE_VERIFICATION_ACCESS_KEY
                                }
                                res = requests.get("https://sms.ru/code/call", params=params, timeout=5)

                                res_json = res.json()
                                request_success = True

                                if res_json["status"] == "OK":
                                    with PhoneVerificationService.__lock:
                                        PhoneVerificationService.__codes[phone]["code"] = res_json["code"]
                                    print(f"Verify code for {phone}: {res_json['code']} (sms)")
                                else:
                                    with PhoneVerificationService.__lock:
                                        PhoneVerificationService.__codes[phone]["code"] = "FAILED"
                                    print(f"Verify code for {phone}: FAILED (sms)")
                            except:
                                if not request_success:
                                    with PhoneVerificationService.__lock:
                                        PhoneVerificationService.__codes[phone]["code"] = "FAILED"
                                traceback.print_exc()
                        else:
                            try:
                                # для бота vk
                                code = random.randint(1000, 9999)

                                params = {
                                    "v": 5.131,
                                    # "user_ids": '352634831,405800248,280108789',  # Гоша, Влад, Норик
                                    "chat_id": '1',  # конфа
                                    "access_token": PHONE_VERIFICATION_ACCESS_KEY,
                                    "message": f"Верификация для номера {phone}<br>Код: {code}",
                                    "random_id": random.randint(1000, 100000000)
                                }
                                res = requests.get("https://api.vk.com/method/messages.send", params=params, timeout=5)
                                # print(res.content)
                                request_success = True
                                obj["code"] = code
                                with PhoneVerificationService.__lock:
                                    PhoneVerificationService.__codes[phone] = obj
                                print(f"Verify code for {phone}: {obj['code']} (vk)")
                            except:
                                if not request_success:
                                    with PhoneVerificationService.__lock:
                                        PhoneVerificationService.__codes[phone]["code"] = "FAILED"
                                traceback.print_exc()

            except:
                traceback.print_exc()

    @staticmethod
    def create():
        if PhoneVerificationService.__instance is None:
            PhoneVerificationService.__instance = Thread(target=PhoneVerificationService.__service_run, daemon=True)
            PhoneVerificationService.__instance.start()

    CHECK_PHONE_NOT_FOUND = "not-found"
    CHECK_PHONE_NOT_READY = "not-ready"
    CHECK_PHONE_FAILED = "failed"
    CHECK_PHONE_INVALID_CODE = "invalid"
    CHECK_PHONE_MAX_ATTEMPTS = "max-attempts"
    CHECK_PHONE_RESEND_LIMIT = "resend"

    @staticmethod
    def check_code(phone: str, code: int, auto_pop=False):
        with PhoneVerificationService.__lock:
            if phone not in PhoneVerificationService.__codes:
                return False, PhoneVerificationService.CHECK_PHONE_NOT_FOUND
            c = PhoneVerificationService.__codes[phone]
            print(f"verify struct: {c}")

            if c["code"] is None:
                # print(f"[PhoneVerificationService] for phone {phone} code not received yet")
                return False, PhoneVerificationService.CHECK_PHONE_NOT_READY

            if c["code"] == "FAILED":
                if auto_pop:
                    PhoneVerificationService.__codes.pop(phone)
                return False, PhoneVerificationService.CHECK_PHONE_FAILED

            if c["attempts"] > 0:
                if c["code"] == code:
                    if auto_pop:
                        PhoneVerificationService.__codes.pop(phone)
                    return True, None

                else:
                    # print(f"invalid code! attempts: {c['attempts']}")
                    PhoneVerificationService.__codes[phone]["attempts"] -= 1
                    return False, PhoneVerificationService.CHECK_PHONE_INVALID_CODE
            else:
                return False, PhoneVerificationService.CHECK_PHONE_MAX_ATTEMPTS

    @staticmethod
    def send_verify(phone: str):
        with PhoneVerificationService.__lock:
            if phone in PhoneVerificationService.__codes:
                c = PhoneVerificationService.__codes[phone]
                if (datetime.now() - c["time"]).total_seconds() <= PHONE_VERIFICATION_RESEND_TIME_SECS:
                    return False, PhoneVerificationService.CHECK_PHONE_RESEND_LIMIT

            PhoneVerificationService.__to_verify.append(phone)
            PhoneVerificationService.__event.set()
            return True, None
