from copy import deepcopy
from datetime import datetime, timedelta

import cv2
import numpy as np
from scipy.signal import argrelmax

from mower.data import agent_list
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.character_recognize import match_avatar
from mower.utils.generate_image import generate_image
from mower.utils.image import cropimg, loadres, thres2
from mower.utils.log import logger
from mower.utils.scene import Scene
from mower.utils.vector import sa, va

name_score = {"维娜·维多利亚": 0.6}


class FightMixin:
    def change_group(self):
        "替换干员组名"
        for group in self.groups:
            flag = True
            for op in group["opers"]:
                name = op["name"]
                self.watching[name] = deepcopy(op)
                if name in self.operators and flag:
                    self.group_to_name[group["name"]] = name
                    flag = False
                    logger.debug(f"{group['name']}替换为{name}")

    def update_watching(self):
        "更新干员的技能用法"
        for op in self.opers:
            name = op["name"]
            self.watching[name] = deepcopy(op)

    def reset(self):
        "重置"
        self.strategies = deepcopy(self.strategies_copy)  # 策略列表
        self.actions = deepcopy(self.actions_copy)  # 行动列表
        self.speed = 1  # 速度
        self.loading = True  # 是否初始化加载
        self.playing = True  # 暂停/继续
        self.clear_op()
        self.location = {}  # 对应位置部署的干员
        self.post_delay = 0  # 行动后延迟
        self.watching = {}  # 核心干员的技能用法
        self.group_to_name = {}  # 干员组名替换的干员名
        self.action_time = datetime.now()  # 行动结束时间，用于计算pre_delay
        self.Bullet_Time = False  # 是否选中干员进入1/5速度

    def fight_init(self):
        "战斗开始时重新初始化"
        self.pause()
        self.check_operators()
        self.update_operators()
        self.change_group()
        self.update_watching()
        self.pre_cost = self.cost()
        self.play()
        self.loading = False

    @property
    def action(self):
        "下一步行动"
        if len(self.actions) > 0:
            return self.actions[0]
        return None

    def complete_action(self):
        "完成当前行动"
        self.actions.pop(0)
        self.action_time = datetime.now()
        self.pre_cost = self.cost()
        if self.post_delay > 0:
            self.sleep_post_delay()

    def sleep_post_delay(self):
        self.sleep(self.post_delay)
        self.post_delay = 0

    def number(self, scope: tp.Scope, height: int, thres: int) -> int:
        "数字识别"
        return config.recog.num.number_int("secret_front", scope, height, thres)

    def kills(self, scope) -> int:
        "获取击杀数"
        img = cropimg(config.recog.gray, scope)
        img = thres2(img, 127)
        sep = loadres("fight/kills_separator", True)
        result = cv2.matchTemplate(img, sep, cv2.TM_SQDIFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
        x = min_loc[0] + scope[0][0] - 1
        kills = self.number((scope[0], (x, scope[1][1])), 28, 127)
        logger.debug(kills)
        return kills

    def cost(self) -> int:
        "获取部署费用"
        cost = self.number(((1800, 745), (1920, 805)), 52, 200)
        logger.debug(cost)
        return cost

    def check_condition(self) -> bool:
        if "kills" in self.action and self.action["kills"] > self.kills():
            return False
        if "costs" in self.action and self.action["costs"] > self.cost():
            return False
        if "cooling" in self.action and self.action["cooling"] > self.CD():
            return False
        if (
            "cost_changes" in self.action
            and self.action["cost_changes"] + self.pre_cost > self.cost()
        ):
            return False
        if "pre_delay" in self.action and (
            datetime.now() - self.action_time
            < timedelta(seconds=self.action["pre_delay"] / 1000)
        ):
            return False
        if "post_delay" in self.action:
            self.post_delay = self.action["post_delay"] / 1000
        if "name" in self.action and self.action["name"] in self.group_to_name:
            self.action["name"] = self.group_to_name[self.action["name"]]
        return True

    def play(self):
        logger.info("继续")
        while (
            self.scene() == Scene.OPERATOR_FIGHT
            and self.find("fight/pause")
            and not self.find("sss/start_directly")
        ):
            self.tap((1800, 80), interval=0.5)

    def pause(self):
        logger.info("暂停")
        while (
            self.scene() == Scene.OPERATOR_FIGHT
            and not self.find("fight/pause")
            and not self.find("sss/start_directly")
        ):
            self.tap((1800, 80), interval=0.5)

    def toggle_speed(self):
        target = 1 if self.speed == 2 else 2
        logger.info(f"切换至{target}倍速")
        self.tap((1650, 80))
        self.speed = target
        self.complete_action()

    def measure_time(func):
        def wrapper(self, *args, **kwargs):
            start = datetime.now()
            result = func(self, *args, **kwargs)
            logger.debug(
                f"{func.__name__}耗时：{(datetime.now() - start).total_seconds() * 1000} ms"
            )
            return result

        return wrapper

    def avatar_recog_pause_decorator(func):
        def wrapper(self, *args, **kwargs):
            if config.conf.avatar_recog_pause:
                self.pause()
            try:
                result = func(self, *args, **kwargs)
            finally:
                if config.conf.avatar_recog_pause:
                    self.play()
            return result

        return wrapper

    @measure_time
    def update_operators_cost_pos(self):
        y = 887
        img = cropimg(config.recog.gray, ((0, y), (1920, 905)))
        threshold = 0.7
        c = loadres("fight/c", True)
        mask = loadres("fight/c_mask", True)
        result = cv2.matchTemplate(img, c, cv2.TM_CCOEFF_NORMED, None, mask)[0]
        op = []
        for i in argrelmax(result, order=50)[0]:
            if result[i] > threshold:
                bar_scope = sa(((-20, 187), (10, 190)), (i, y))
                img = cropimg(config.recog.hsv, bar_scope)
                img = cv2.inRange(img, (1, 0, 0), (3, 255, 255))
                count = cv2.countNonZero(img)
                if count < 50:
                    op.append(i)
        return op

    @avatar_recog_pause_decorator
    @measure_time
    def update_operators(self):
        "识别可部署的干员"
        try:
            y = 887
            segment = []
            cost = []
            self.operators = {}
            self.tool_men = {}
            for x in self.op:
                scope = sa(((-84, 58), (56, 153)), (x, y))
                segment.append(scope)
                cost_scope = sa(((-13, 19), (30, 44)), (x, y))
                cost.append(self.number(cost_scope, 25, 80))
            operators = match_avatar(config.recog.gray, segment)
            self.operators_num = len(operators)
            for idx, (name, scope) in enumerate(operators):
                self.operators[name] = {"scope": scope, "cost": cost[idx]}
                if name not in self.watching and name in agent_list:
                    profession = agent_list[name]["profession"]
                    if (
                        profession not in self.tool_men
                        or cost[idx] < self.operators[self.tool_men[profession]]["cost"]
                    ):
                        self.tool_men[profession] = name

            logger.debug(f"{self.operators=} {self.tool_men=}")
        except Exception as e:
            logger.exception(e)
            return

    def check_operators(self):
        self.op = self.update_operators_cost_pos()
        logger.debug(f"{len(self.op)=} {self.operators_num=}")
        if len(self.op) == self.operators_num:
            return True
        return False

    def clear_op(self):
        self.operators = {}
        self.tool_men = {}
        self.op = []
        self.operators_num = 0

    def select(self, x: int, y: int):
        "选中干员"
        pos = self.calc.get_character_screen_pos(x, y, False, False)
        pos = int(pos.x), int(pos.y)
        self.tap(pos)

    def withdraw(self):
        "撤下干员"
        if "location" in self.action:
            x, y = self.action["location"]
        else:
            for loc, name in self.location.items():
                if name == self.action["name"]:
                    x, y = loc
                    break
        if not self.Bullet_Time:
            self.select(x, y)
            self.sleep(0.5)
        else:
            self.Bullet_Time = False
        pos = self.calc.get_with_draw_screen_pos(x, y)
        pos = int(pos.x), int(pos.y)
        self.tap(pos)
        self.sleep(0.5)
        self.clear_op()
        self.complete_action()

    def use_skill(self, x, y):
        "开技能"
        start_time = datetime.now()
        if not self.Bullet_Time:
            self.select(x, y)
            if not self.check_operators():  # 利用这个空档期更新可部署干员
                self.update_operators()
            elapsed_time = datetime.now() - start_time
            remaining_time = max(timedelta(seconds=0.4) - elapsed_time, timedelta(0))
            self.sleep(remaining_time.total_seconds())
        else:
            self.Bullet_Time = False
        pos = self.calc.get_skill_screen_pos(x, y)
        pos = int(pos.x), int(pos.y)
        self.tap(pos)
        self.sleep(0.4)
        if self.find("fight/attack") or self.find("fight/defend"):
            self.tap((20, 20))
            return False
        return True

    def action_skill(self):
        "使用action中指定位置的技能"
        if "location" in self.action:
            x, y = self.action["location"]
        else:
            for loc, name in self.location.items():
                if name == self.action["name"]:
                    x, y = loc
                    break
        if self.use_skill(x, y):
            self.complete_action()

    def change_skill_usage(self):
        "修改干员技能用法"
        skill_usage = self.action["skill_usage"]
        self.watching[self.action["name"]]["skill_usage"] = skill_usage
        if skill_usage == 2:
            self.watching[self.action["name"]]["skill_times"] = self.action[
                "skill_times"
            ]
        self.complete_action()

    def bullet_time(self):
        "选中干员进入1/5速度"
        logger.info(f"{self.action["name"]}")
        if "name" in self.action and ((name := self.action["name"]) in self.operators):
            self.tap(self.operators[name]["scope"])
            self.Bullet_Time = True
            self.complete_action()
            return
        if "location" in self.action:
            x, y = self.action["location"]
        else:
            for loc, name in self.location.items():
                if name == self.action["name"]:
                    x, y = loc
                    break
        self.select(x, y)
        self.Bullet_Time = True
        self.complete_action()

    @measure_time
    def check_name(self, name: str, scope: tp.Scope) -> bool:
        "检查部署干员名字是否正确"
        res = generate_image(name, 33)
        img = cropimg(config.recog.gray, scope)
        result = cv2.matchTemplate(img, res, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
        logger.debug(f"{name=} {max_val=}")
        score = name_score.get(name, 0.7)
        return max_val > score

    def drag_success(self):
        "判断第一段拖动是否成功"
        try:
            img = thres2(config.recog.gray, 190)
            contours, _ = cv2.findContours(
                img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
            )
            rect = [cv2.boundingRect(c) for c in contours]
            left = min((x for x, y, w, h in rect if w > 10 and h > 10), default=None)
            right = max(
                (x + w for x, y, w, h in rect if w > 10 and h > 10), default=None
            )
            top = min((y for x, y, w, h in rect if w > 10 and h > 10), default=None)
            bottom = max(
                (y + h for x, y, w, h in rect if w > 10 and h > 10), default=None
            )
            if any(v is None for v in [left, right, top, bottom]):
                return False
            width = right - left
            height = bottom - top
            logger.debug(f"{width=} {height=}")
            return width < 950 and height < 850
        except Exception as e:
            logger.exception(e)
            return False

    def CD(self) -> int:
        "获取进入CD的干员数量"
        y = 887
        img = cropimg(config.recog.gray, ((0, y), (1920, 905)))
        threshold = 0.7
        c = loadres("fight/c", True)
        mask = loadres("fight/c_mask", True)
        result = cv2.matchTemplate(img, c, cv2.TM_CCOEFF_NORMED, None, mask)[0]
        op = []
        for i in argrelmax(result, order=50)[0]:
            if result[i] > threshold:
                op.append(i)
        number = 0
        for x in op:
            # 看最下方条的颜色判断是否正在转CD
            bar_scope = sa(((-20, 187), (10, 190)), (x, y))
            img = cropimg(config.recog.hsv, bar_scope)
            img = cv2.inRange(img, (1, 0, 0), (3, 255, 255))
            count = cv2.countNonZero(img)
            logger.debug(count)
            if count > 50:
                number += 1
        return number

    def skill_ready(self, x: int, y: int) -> bool:
        """指定坐标的干员技能是否可以开启

        Args:
            x: 横坐标
            y: 纵坐标
        """
        try:
            skill_ready = loadres("fight/skill_ready", True)
            pos = self.calc.get_character_screen_pos(x, y, False, False)
            pos = int(pos.x), int(pos.y)
            img = cropimg(config.recog.gray, sa(((-25, -168), (25, -118)), pos))
            mask = np.ones_like(skill_ready, dtype=np.float32)
            mask[8:17, :] = 0.3
            result = cv2.matchTemplate(
                img, skill_ready, cv2.TM_CCOEFF_NORMED, mask=mask
            )
            min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
            logger.debug(f"{max_val=} {max_loc=} {x=} {y=}")
            return max_val >= 0.65
        except Exception as e:
            logger.exception(e)
            return False

    def skill_stop(self, x: int, y: int) -> bool:
        """指定坐标的干员技能是否可以关闭

        Args:
            x: 横坐标
            y: 纵坐标
        """
        try:
            skill_stop = loadres("fight/skill_stop")
            h, w, _ = skill_stop.shape
            pos = self.calc.get_character_screen_pos(x, y, False, False)
            pos = int(pos.x), int(pos.y)
            img = cropimg(config.recog.img, sa(((-15, -168), (15, -138)), pos))
            result = cv2.matchTemplate(img, skill_stop, cv2.TM_SQDIFF_NORMED)
            min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
            logger.debug(f"{min_val=} {min_loc=}")
            return min_val <= 0.2
        except Exception as e:
            logger.exception(e)
            return False

    def deploy(self, name: str, x: int, y: int, direction: str):
        "策略的干员部署"
        if self.Bullet_Time:
            self.Bullet_Time = False
        if name not in self.operators:
            if not self.check_operators():
                self.update_operators()
            return False
        if self.cost() < self.operators[name]["cost"]:
            return False
        start = self.get_pos(self.operators[name]["scope"])
        pos = self.calc.get_character_screen_pos(x, y, True, False)
        pos = int(pos.x), int(pos.y + 20)
        logger.info(f"在({x}, {y})部署{name}，方向为{direction}")
        if direction == "Left":
            dir = (-400, 0)
        elif direction == "Right":
            dir = (400, 0)
        elif direction == "Up":
            dir = (0, -400)
        else:
            dir = (0, 400)
        dir = va(pos, dir)
        self.swipe_ext([start, pos], [config.conf.first_swipe_duration], 100, 0.05)
        if direction == "None":
            self.clear_op()
            self.location[(x, y)] = name
            return True

        if not self.check_name(name):
            self.ctap(va(pos, (-300, -300)), 0.5, config.screenshot_avg / 1000)
            self.clear_op()
            return False

        for _ in range(5):
            if not (flag := self.drag_success()):
                self.sleep(0.1)
            else:
                break

        if flag:
            self.swipe_ext([pos, dir], [config.conf.second_swipe_duration], 0, 0.4)
            if self.drag_success():
                self.ctap(va(pos, (-300, -300)), 0.5, config.screenshot_avg / 1000)
                self.clear_op()
                return False
            self.clear_op()
            self.location[(x, y)] = name
            return True
        else:
            self.ctap(va(pos, (-300, -300)), 0.5, config.screenshot_avg / 1000)
            self.clear_op()
            return False

    def travel_watching_skills(self):
        "检查每个干员技能并释放"
        for (x, y), name in self.location.items():
            if name in self.watching and (
                (self.watching[name]["skill_usage"]) == 0
                or (
                    self.watching[name]["skill_usage"] == 2
                    and self.watching[name]["skill_times"] <= 0
                )
            ):
                continue
            if self.skill_ready(x, y):
                if self.use_skill(x, y):
                    if (
                        name in self.watching
                        and self.watching[name]["skill_usage"] == 2
                    ):
                        self.watching[name]["skill_times"] -= 1
                        logger.info(
                            f"{name}技能剩余次数：{self.watching[name]['skill_times']}"
                        )

    def execute_action(self):
        if self.action is None:
            return
        if not self.check_condition():
            return
        logger.debug(self.action)
        if self.action["type"] == "SpeedUp":
            self.toggle_speed()
        elif self.action["type"] == "Deploy":
            name = self.action["name"]
            x, y = self.action["location"]
            direction = self.action["direction"]
            if self.deploy(name, x, y, direction):
                self.complete_action()
        elif self.action["type"] == "Retreat":
            self.withdraw()
        elif self.action["type"] == "Skill":
            self.action_skill()

        elif self.action["type"] == "SkillUsage":
            self.change_skill_usage()
        elif self.action["type"] == "BulletTime":
            self.bullet_time()

        elif self.action["type"] == "调配干员":
            if not self.draw_as_possible and self.find("sss/add_agent"):
                self.tap((1805, 684), interval=0.05)
            self.complete_action()

        else:
            self.complete_action()
