import abc
import threading

from conf import Config
from tool.typing import *
from tool.time import HGSTime
from .garbage import GarbageBag, GarbageType


class UserNotSupportError(Exception):
    pass


class UserType:
    UserTypeStrList: List = ["", "NORMAL", "MANAGER"]
    normal: enum = 1
    manager: enum = 2


class User(metaclass=abc.ABCMeta):
    def __init__(self, name: uname_t, uid: uid_t, user_type: enum, destruct_call: Optional[Callable]):
        self._name: uname_t = uname_t(name)
        self._uid: uid_t = uid_t(uid)
        self._type: enum = enum(user_type)
        self._lock = threading.RLock()  # 用户 互斥锁
        self._destruct_call = destruct_call

    def __del__(self):
        if self._destruct_call is None:
            return

        _destruct_call = self._destruct_call
        self._destruct_call = None
        _destruct_call(self)

    def destruct(self):
        self.__del__()

    def is_manager(self):
        try:
            self._lock.acquire()
            _type = self._type
        finally:
            self._lock.release()

        return _type == UserType.manager

    def get_uid(self) -> uid_t:
        try:
            self._lock.acquire()
            uid = self._uid
        finally:
            self._lock.release()

        return uid

    def get_name(self) -> uname_t:
        try:
            self._lock.acquire()
            name = self._name
        finally:
            self._lock.release()

        return name

    def get_user_type(self) -> enum:
        try:
            self._lock.acquire()
            _type = self._type
        finally:
            self._lock.release()

        return _type

    def get_info(self) -> Dict[str, str]:
        raise UserNotSupportError

    def __repr__(self):
        try:
            self._lock.acquire()
            _type = UserType.UserTypeStrList[self._type]
            uid = self._uid
            name = self._name
        finally:
            self._lock.release()
        return f"User {uid} {name} is {_type}"

    def evaluate(self, is_right: bool) -> score_t:
        raise UserNotSupportError

    def get_score(self):
        raise UserNotSupportError

    def get_reputation(self):
        raise UserNotSupportError

    def get_rubbish(self):
        raise UserNotSupportError

    def add_score(self, score: score_t) -> score_t:
        raise UserNotSupportError

    def throw_rubbish(self, garbage: GarbageBag, garbage_type: enum, loc: location_t) -> bool:
        raise UserNotSupportError

    def check_rubbish(self, garbage: GarbageBag, right: bool, user) -> bool:
        raise UserNotSupportError


class NormalUser(User):
    def __init__(self, name: uname_t,
                 uid: uid_t,
                 reputation: score_t,
                 rubbish: count_t,
                 score: score_t,
                 destruct_call: Optional[Callable]):
        super(NormalUser, self).__init__(name, uid, UserType.normal, destruct_call)
        self._reputation = score_t(reputation)
        self._rubbish = count_t(rubbish)
        self._score = score_t(score)

    def __repr__(self):
        try:
            self._lock.acquire()
            info = (f"User {self._uid} {self._name} "
                    f"reputation {self._reputation} "
                    f"rubbish {self._rubbish} "
                    f"score {self._score} "
                    f"is NORMAL")
        finally:
            self._lock.release()
        return info

    def get_info(self) -> Dict[str, str]:
        """
        获取当前用户的简单信息
        :return: 用户信息字典
        """
        try:
            self._lock.acquire()
            info = {
                "name": str(self._name),
                "uid": str(self._uid),
                "manager": '0',
                "reputation": str(self._reputation),
                "rubbish": str(self._rubbish),
                "score": str(self._score)
            }
        finally:
            self._lock.release()

        return info

    def evaluate(self, is_right: bool) -> score_t:
        """
        评估信誉积分
        使用朴素贝叶斯公式为基础
        总分值: 1000分
        实际分: P(A|B) * 1000
        A=能正确仍对垃圾的概率
        B=本次仍对垃圾的概率

        初始概率:
          P(A) = 0.3  (default_reputation = 300)
          P(B|A) = 0.6
          P(B|^A) = 0.3
          P(B) = 0.8 * 0.3 + 0.1 * 0.7 = 0.31
          p(^B) = 0.69

          P(A|B) = P(A) * (P(B|A) / P(B)) = P(A) * 2.5806
          P(A|^B) = P(A) * (P(^B|A) / P(^B)) = P(A) * (0.2/0.96) = P(A) * 0.2083
        :return: 信誉积分
        """
        try:
            self._lock.acquire()
            if is_right and self._rubbish > Config.max_rubbish_week:
                return self._reputation  # 执行 finally将释放锁

            pa = self._reputation / 1000  # P(A)
            if pa < 0.01:
                pa = 0.01
            p_a = 1 - pa  # P(^A)
            pba, p_ba, pb_a, p_b_a = 0.6, 0.4, 0.3, 0.7  # P(B|A), P(^B|A), P(B|^A), P(^B|^A)
            pb = pba * pa + pb_a * p_a  # P(B) = P(B|A) * P(A) + P(B|^A) * P(^A)
            p_b = p_ba * pa + p_b_a * p_a  # P(^B) = P(^B|A) * P(A) + P(^B|^A) * P(^A)

            if is_right:
                new_pa = pa * (pba / pb)  # P(A|B)
            else:
                new_pa = pa * (p_ba / p_b)  # P(A|^B)
            new_pa = new_pa * 1000
            if int(new_pa) == 0:
                new_pa = 1
            if int(new_pa) > 1000:
                new_pa = 999

            amplitude = new_pa - self._reputation  # 分差
            amplitude_top = 1000 - self._reputation  # 距离总分分差

            if is_right:
                if amplitude >= 20:
                    amplitude = amplitude * (amplitude_top / 1000)  # 涨分抑制
            else:
                if amplitude <= -20:
                    amplitude = amplitude * (self._reputation / 1000)  # 总分分差月小扣分越高

            self._reputation += int(amplitude)
            if self._reputation <= 5:
                self._reputation = 5
            reputation = self._reputation
        finally:
            self._lock.release()

        return reputation

    def get_reputation(self):
        raise self._reputation

    def get_rubbish(self):
        raise self._reputation

    def get_score(self):
        return self._score

    def add_score(self, score: score_t) -> score_t:
        try:
            self._lock.acquire()
            tmp = self._score + score
            if tmp < 0:
                self._score = 0
                return 0
            elif tmp > Config.max_score:
                self._score = Config.max_score
                return Config.max_score

            self._score += score
            score = self._score
        finally:
            self._lock.release()
        return score

    def throw_rubbish(self, garbage: GarbageBag, garbage_type: enum, loc: location_t = Config.base_location) -> bool:
        """
        垃圾投放
        :param garbage: 垃圾袋
        :param garbage_type: 垃圾类别
        :param loc: 地区
        :return:
        """
        try:
            self._lock.acquire()
            if self._rubbish > Config.max_rubbish_week:  # 大于上限, 扣分
                self.add_score(-3)
            elif self._rubbish > Config.limit_rubbish_week:  # 大于最大上限, 不再允许扔垃圾
                return False

            if garbage.is_use() or garbage.is_check()[0]:  # 垃圾袋被使用
                return False
            garbage.config_use(garbage_type, HGSTime(), self._uid, loc)

            self._rubbish += 1
        finally:
            self._lock.release()
        return True

    def check_rubbish(self, garbage: GarbageBag, right: bool, user: User) -> bool:
        raise UserNotSupportError


class ManagerUser(User):
    def __init__(self, name: uname_t,
                 uid: uid_t,
                 destruct_call: Optional[Callable]):
        super(ManagerUser, self).__init__(name, uid, UserType.manager, destruct_call)

    def check_rubbish(self, garbage: GarbageBag, result: bool, user: User) -> bool:
        """
        检查垃圾
        :param garbage: 垃圾袋
        :param result: 结果
        :param user: 垃圾袋投放者
        :return:
        """
        if (not garbage.is_use()) or garbage.is_check()[0] or user.get_uid() != garbage.get_user():  # 调用时已经有锁
            return False

        try:
            self._lock.acquire()
            garbage.config_check(result, self._uid)
            user.evaluate(result)

            if result:
                if garbage.get_type() == GarbageType.recyclable:
                    user.add_score(3)
                elif garbage.get_type() == GarbageType.kitchen or garbage.get_type() == GarbageType.hazardous:
                    user.add_score(2)
                else:
                    user.add_score(1)
            else:
                user.add_score(-4)
        finally:
            self._lock.release()

        return True

    def get_info(self) -> Dict[str, str]:
        try:
            self._lock.acquire()
            info = {
                "name": str(self._name),
                "uid": str(self._uid),
                "manager": '1'
            }
        finally:
            self._lock.release()
        return info

    def get_reputation(self):
        raise 0

    def get_rubbish(self):
        raise 0

    def get_score(self):
        return 0
