from copy import deepcopy
from datetime import datetime, timedelta

import cv2
import numpy as np
from scipy.signal import argrelmax
from skimage.metrics import structural_similarity

from mower.data import agent_list
from mower.models import Digtal
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.character_recognize import match_avatar
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, loadres, thres2
from mower.utils.log import logger
from mower.utils.scene import Scene
from mower.utils.tile_pos import Calc, find_level
from mower.utils.vector import sa, va

from .sss_drop import SSSDROPSolver
from .utils import translate


class SSSFightSolver(SceneGraphSolver):
    def run(
        self,
        level_name: str,
        opers: list,
        strategies: list,
        draw_as_possible: bool,
        actions: list,
        drops: list,
        retry_times: int,
        groups: list = [],
        deadline: datetime = None,
        blacklist: list = [],
    ):
        """
        Args:
            level_name: 关卡名
            opers: 干员列表
            strategies: 策略列表
            draw_as_possible: 调配干员好了就点
            actions: 行动列表
            drops: 增调顺序
            retry_times: 重试次数，超过返回，结束保全
            groups: 干员组列表
            deadline: 截止时间
            blacklist: 增调干员黑名单
        """
        logger.info(f"Start: 自动保全战斗：{level_name}")
        logger.info("地图坐标计算：https://github.com/yuanyan3060/Arknights-Tile-Pos")
        level = find_level(None, level_name)
        self.calc = Calc(1920, 1080, level)
        self.level_name = level_name
        self.strategies_copy = strategies  # 策略列表备份
        self.actions_copy = actions  # 行动列表备份
        self.draw_as_possible = draw_as_possible  # 调配干员好了就点
        self.opers: list[dict] = opers  # 干员列表
        self.drops = drops  # 增调顺序
        self.retry_times = retry_times  # 重试次数，超过返回
        self.groups = groups  # 干员组
        self.deadline = deadline  # 截止时间
        self.recog_with_stop = (
            config.conf.avatar_recog_pause
        )  # 是否在识别可部署干员时暂停
        self.blacklist = blacklist  # 增调干员黑名单
        self.success = False
        super().run()
        return self.success

    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.operators = {}  # 可部署干员
        self.location = {}  # 对应位置部署的干员
        self.post_delay = 0  # 行动后延迟
        self.watching = {}  # 核心干员的技能用法
        self.replacements = {}  # 要替换的干员组名
        self.cores = set()  # 核心干员
        self.action_time = datetime.now()

    def init_cores(self):
        for strategy in self.strategies:
            if "core" in strategy:
                self.cores.add(strategy["core"])

    def change_group(self):
        for group in self.groups:
            for op in group["opers"]:
                self.replacements[op["name"]] = group["name"]
                op["name"] = group["name"]
                self.opers.append(op)

    def update_watching(self):
        for op in self.opers:
            name = op["name"]
            self.watching[name] = deepcopy(op)

    def number(self, scope: tp.Scope, height: int, thres: int) -> int:
        "数字识别"
        try:
            img = cropimg(config.recog.gray, scope)
            default_height = 25
            if height != default_height:
                scale = 25 / height
                img = cv2.resize(img, None, None, scale, scale)
            img = thres2(img, thres)
            contours, _ = cv2.findContours(
                img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
            )
            rect = [cv2.boundingRect(c) for c in contours]
            rect.sort(key=lambda c: c[0])

            value = 0

            for x, y, w, h in rect:
                digit = cropimg(img, ((x, y), (x + w, y + h)))
                digit = cv2.copyMakeBorder(
                    digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
                )
                score = []
                for i in range(10):
                    im = Digtal().secret_front[i]
                    result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
                    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
                    score.append(min_val)
                value = value * 10 + score.index(min(score))

            return value
        except Exception:
            return 0

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

    def kills(self) -> int:
        "获取击杀数"
        img = cropimg(config.recog.gray, ((935, 30), (1085, 60)))
        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] + 934
        kills = self.number(((935, 30), (x, 60)), 28, 127)
        logger.debug(kills)
        return kills

    def CD(self) -> int:
        "获取进入CD的干员数量"
        config.recog.update()
        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)
        self.operators = {}
        number = 0
        for x in op:
            # 看最下方条的颜色判断是否正在转CD
            bar_scope = sa(((-20, 187), (10, 190)), (x, y))
            img = cropimg(config.recog.img, 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 sleep_post_delay(self):
        self.sleep(self.post_delay)
        self.post_delay = 0

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

    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(((-15, -168), (20, -128)), 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=}")
            return max_val >= 0.65
        except Exception:
            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:
            return False

    def update_operators(self):
        "识别可部署的干员"
        try:
            config.recog.update()
            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)
            segment = []
            cost = []
            self.operators = {}
            for x in op:
                # 看最下方条的颜色判断是否正在转CD
                bar_scope = sa(((-20, 187), (10, 190)), (x, y))
                img = cropimg(config.recog.img, bar_scope)
                img = cv2.inRange(img, (1, 0, 0), (3, 255, 255))
                count = cv2.countNonZero(img)
                logger.debug(count)
                if count > 50:
                    continue

                scope = sa(((-58, 53), (42, 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.img, segment)
            for idx, (name, scope) in enumerate(operators):
                name = self.replacements[name] if name in self.replacements else name
                if name not in self.cores and name in agent_list:
                    name = translate[agent_list[name]["profession"]]
                    if name in self.operators:
                        continue
                self.operators[name] = {"scope": scope, "cost": cost[idx]}
            logger.debug(self.operators)
        except Exception:
            return

    @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()
        if self.post_delay > 0:
            self.sleep_post_delay()

    def toggle_play(self):
        "切换暂停与继续"
        config.device.tap((1800, 80))
        self.sleep(0.5)
        img = cropimg(config.recog.gray, ((740, 480), (1180, 665)))
        img = thres2(img, 250)
        res = loadres("fight/pause", True)
        ssim = structural_similarity(img, res)
        logger.debug(ssim)
        self.playing = ssim <= 0.9

    def play(self):
        logger.info("继续")
        while not self.playing and self.scene() == Scene.OPERATOR_FIGHT:
            self.toggle_play()

    def pause(self):
        logger.info("暂停")
        while self.playing and self.scene() == Scene.OPERATOR_FIGHT:
            self.toggle_play()

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

    def bullet_time(self):
        "选中干员进入1/5速度"
        if "name" in self.action:
            name = self.action["name"]
            x, y = self.location[name]
            self.location[name] = None
        else:
            x, y = self.action["location"]
        self.select(x, y)
        self.Bullet_Time = True
        self.complete_action()

    def drag_success(self):
        try:
            img = thres2(config.recog.gray, 200)
            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 > 20 and h > 20)
            right = max(x + w for x, y, w, h in rect if w > 20 and h > 20)
            top = min(y for x, y, w, h in rect if w > 20 and h > 20)
            bottom = max(y + h for x, y, w, h in rect if w > 20 and h > 20)
            width = right - left
            height = bottom - top
            logger.debug(f"{width=} {height=}")
            return width < 900 and height < 850
        except Exception:
            return False

    def deploy(self):
        "部署干员"
        name = self.action["name"]
        if name not in self.operators:
            self.update_operators()
            return
        if self.cost() < self.operators[name]["cost"]:
            return
        start = self.get_pos(self.operators[name]["scope"])
        top = va(start, (0, -100))
        x, y = self.action["location"]
        pos = self.calc.get_character_screen_pos(x, y, True, False)
        pos = int(pos.x), int(pos.y)
        direction = self.action["direction"]
        logger.info(f"在({x}, {y})部署{name}，方向为{direction}")
        if direction in ["Left", "左"]:
            dir = (-200, 0)
        elif direction in ["Right", "右"]:
            dir = (200, 0)
        elif direction in ["Up", "上"]:
            dir = (0, -200)
        else:
            dir = (0, 200)
        dir = va(pos, dir)
        config.device.swipe_ext([start, top, pos], [100, 500], up_wait=400)
        if direction in ["None", "无"]:
            self.operators = {}
            self.complete_action()
            self.location[(x, y)] = name
            return

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

        if flag:
            config.device.swipe_ext([pos, dir], [200])
            self.sleep(0.5)
            if self.drag_success():
                self.ctap(va(pos, (-300, -300)), 0.5, config.screenshot_avg / 1000)
                return
            self.operators = {}
            self.complete_action()
            self.location[(x, y)] = name
        else:
            self.ctap(va(pos, (-300, -300)), 0.5, config.screenshot_avg / 1000)

    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)
        config.device.tap(pos)

    def withdraw(self):
        "撤下干员"
        if "name" in self.action:
            name = self.action["name"]
            x, y = self.location[name]
            self.location[name] = None
        else:
            x, y = self.action["location"]
        self.select(x, y)
        self.sleep(0.5)
        pos = self.calc.get_with_draw_screen_pos(x, y)
        pos = int(pos.x), int(pos.y)
        config.device.tap(pos)
        self.sleep(0.5)
        self.operators = {}
        self.complete_action()

    def use_skill(self, x, y):
        "开技能"
        self.select(x, y)
        self.sleep(0.4)
        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"):
            self.tap((20, 20))
            return False
        return True

    def action_skill(self):
        "使用action中指定位置的技能"
        if flag := ("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.skill_ready(x, y) or self.skill_stop(x, y):
            if self.use_skill(x, y):
                if not flag and self.watching[self.action["name"]]["skill_usage"] == 2:
                    self.watching[self.action["name"]]["skill_times"] -= 1
                    if self.watching[self.action["name"]]["skill_times"] <= 0:
                        del self.watching[self.action["name"]]

            self.complete_action()

    def strategy_deploy(self, name: str, x: int, y: int, direction: str):
        "策略的干员部署"
        if name not in self.operators:
            self.update_operators()
            return False
        if self.cost() < self.operators[name]["cost"]:
            return False
        start = self.get_pos(self.operators[name]["scope"])
        top = va(start, (0, -100))
        pos = self.calc.get_character_screen_pos(x, y, True, False)
        pos = int(pos.x), int(pos.y)
        logger.info(f"在({x}, {y})部署{name}，方向为{direction}")
        if direction in ["Left", "左"]:
            dir = (-200, 0)
        elif direction in ["Right", "右"]:
            dir = (200, 0)
        elif direction in ["Up", "上"]:
            dir = (0, -200)
        else:
            dir = (0, 200)
        dir = va(pos, dir)
        config.device.swipe_ext([start, top, pos], [100, 500], up_wait=400)
        if direction in ["None", "无"]:
            self.operators = {}
            self.location[(x, y)] = name
            return True

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

        if flag:
            config.device.swipe_ext([pos, dir], [200])
            self.sleep(0.5)
            if self.drag_success():
                self.ctap(va(pos, (-300, -300)), 0.5, config.screenshot_avg / 1000)
                return False
            self.operators = {}
            self.location[(x, y)] = name
            return True
        else:
            self.ctap(va(pos, (-300, -300)), 0.5, config.screenshot_avg / 1000)
            return False

    def check_tool_men_complete(self, tool_men: dict):
        "检查策略中的工具人是否部署完成"
        for _, num in tool_men.items():
            if num > 0:
                return False
        return True

    def travel_strategies(self):
        "遍历策略"
        if len(self.strategies) == 0:
            return
        if self.recog_with_stop:
            self.pause()
        self.update_operators()
        if self.recog_with_stop:
            self.play()
        continue_loc = set()

        for i in range(0, len(self.strategies) - 1):
            strategy = self.strategies[i]
            x, y = strategy["location"]
            if (x, y) in continue_loc:
                continue
            logger.info(strategy)
            if cis := ("core" in strategy):
                continue_loc.add((x, y))
            if not self.check_tool_men_complete(strategy["tool_men"]):
                deploy_name = None
                cost = 999
                for name, num in strategy["tool_men"].items():
                    if num > 0:
                        E_name = name
                        name = translate[name]
                        if (
                            name in self.operators
                            and self.operators[name]["cost"] < cost
                        ):
                            deploy_name = name
                            cost = self.operators[name]["cost"]
                if deploy_name:
                    if self.strategy_deploy(deploy_name, x, y, strategy["direction"]):
                        strategy["tool_men"][E_name] -= 1
                        if strategy["tool_men"][E_name] == 0 and not cis:
                            del self.strategies[i]
                    return
                else:
                    continue
            if not cis:
                del self.strategies[i]
                return

            if strategy["core"] in self.operators:
                if self.strategy_deploy(strategy["core"], x, y, strategy["direction"]):
                    del self.strategies[i]
                return

    def transition(self):
        if (scene := self.scene()) == Scene.OPERATOR_FIGHT:
            if self.loading:
                self.loading = False
                self.init_cores()
                self.change_group()
                self.update_operators()
                self.update_watching()
                return
            if self.draw_as_possible and self.find("sss/add_agent"):
                self.tap((1805, 684), interval=0.4)
                self.operators = {}
                return
            elif self.find("sss/start_directly"):
                self.tap_element("sss/start_directly")
                return
            for (x, y), name in self.location.items():
                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']}"
                            )
                            if self.watching[name]["skill_times"] <= 0:
                                del self.location[(x, y)]
                                break

            if self.action is None:
                self.travel_strategies()
                return
            if not self.check_condition():
                self.travel_strategies()
                return

            logger.debug(self.action)
            if self.action["type"] in ["SpeedUp", "二倍速"]:
                self.toggle_speed()
            elif self.action["type"] in ["Deploy", "部署"]:
                self.deploy()
            elif self.action["type"] in ["Retreat", "撤退"]:
                self.withdraw()
            elif self.action["type"] in ["Skill", "技能"]:
                self.action_skill()
            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()
            elif (
                self.action["type"] == "CheckIfStartOver"
            ):  # 检查干员在不在，不在就退出重开,暂未支持,没看见作业用到的
                self.complete_action()

            self.travel_strategies()

        elif scene == Scene.OPERATOR_FAILED:
            logger.warning(f"{self.level_name}作战失败，请检查干员/练度")
            self.tap((960, 540))
            self.retry_times -= 1

        elif scene == Scene.SSS_OPERATION_COMPLETE:
            logger.info(f"{self.level_name}作战胜利")
            self.tap((960, 540))
            self.success = True

        elif scene in self.waiting_scene:
            self.waiting_solver()
        elif scene in [Scene.SSS_DEPLOY, Scene.SSS_REDEPLOY]:
            if self.deadline and self.deadline < datetime.now():  # 任务打断
                self.success = True
                return True
            if self.success or self.retry_times <= 0:
                return True
            else:
                self.tap((1645, 971))
                self.reset()
        elif scene in [
            Scene.SSS_DROP_AGENT_BEFORE_FIGHT,
            Scene.SSS_DROP_EC_BEFORE_FIGHT,
            Scene.SSS_DROP_IN_FIGHT,
        ]:
            self.sleep(2)
            if not SSSDROPSolver().run(self.drops, self.blacklist):
                self.scene_graph_navigation(Scene.SSS_DEPLOY)
                return True
        elif scene == Scene.SSS_ACTION:
            self.tap((1651, 967))
        elif scene == Scene.SSS_ACCOMPLISHED:
            return True
        else:
            return True
