import json
import math
import os
import time
from hardware_control import hw_ctrl

# 修理商所在位置（游戏坐标），按实际位置修改
VENDOR_POS = (30.08, 71.51)
# 到达判定距离（与 coordinate_patrol 一致）
VENDOR_ARRIVAL_THRESHOLD = 0.1
# 修理商所在位置配置文件
VENDOR_FILE = 'vendor.json'


class LogisticsManager:
    def __init__(self, route_file=None):
        self.need_repair = False
        self.bag_full = False
        self.is_returning = False
        self.route_file = route_file or VENDOR_FILE
        self.bag_full_hearthstone = False  # 包满时用炉石回城而非走路修理
        self.hearthstone_key = "b"         # 炉石按键
        self.hearthstone_cast_sec = 12.0   # 炉石施法等待秒数
        self.enable_bag_full_mail = False
        self.logistics_full_auto = False
        self.bag_slot_threshold = 2
        self.durability_threshold = 0.2
        self.mailbox_route_file = os.path.join("recorder", "mailbox.json")
        self.mailbox_interact_key = "8"
        self.mail_recipient_key = ""
        self.mail_send_key = "f8"
        self.mailbox_open_wait_sec = 2.0
        self.mail_send_wait_sec = 60.0
        self.repair_target_key = "8"
        self.repair_interact_key = "4"
        self.repair_interact_wait_sec = 2.0

    def _resolve_path(self, path):
        if not path:
            return ""
        path = str(path)
        if os.path.isabs(path):
            return path
        cwd_path = os.path.abspath(path)
        if os.path.exists(cwd_path):
            return cwd_path
        return os.path.join(os.path.dirname(os.path.abspath(__file__)), path)

    def _sleep_with_stop(self, seconds, stop_check=None):
        end_at = time.time() + max(0.0, float(seconds))
        while time.time() < end_at:
            if callable(stop_check) and stop_check():
                return False
            time.sleep(min(0.1, end_at - time.time()))
        return True

    def _read_position(self, get_state):
        if not callable(get_state):
            return None
        st = get_state()
        if not st:
            return None
        try:
            x = st.get("x")
            y = st.get("y")
            if x is None or y is None:
                return None
            return float(x), float(y)
        except Exception:
            return None
        
    def _state_bag_full(self, state):
        try:
            return int(state.get("free_slots", 0) or 0) < int(self.bag_slot_threshold)
        except Exception:
            return False

    def _state_durability_low(self, state):
        try:
            threshold = float(self.durability_threshold)
            if threshold > 1.0:
                threshold = threshold / 100.0
            durability = state.get("durability", 1.0)
            if durability is None:
                return False
            return float(durability) < threshold
        except Exception:
            return False

    def get_pending_tasks(self, state):
        tasks = []
        bag_full = self._state_bag_full(state)
        durability_low = self._state_durability_low(state)

        if bag_full and self.enable_bag_full_mail:
            tasks.append("mail")
        if durability_low:
            tasks.append("repair")
        if not tasks and bag_full and self.bag_full_hearthstone:
            tasks.append("hearthstone_stop")
        return tasks

    def check_logistics(self, state):
        """
        state['free_slots']: 剩余空格数量
        state['durability']: 0.0 ~ 1.0 的耐久度
        """
        # 触发阈值：空格少于配置值，或耐久度低于配置值。
        if self.get_pending_tasks(state):
            if not self.is_returning:
                print(f">>> [后勤警告] 背包/耐久不足！触发回城程序。")
                self.is_returning = True
        else:
            self.is_returning = False

    def use_hearthstone_and_stop(self, get_state=None):
        """按炉石按键并等待施法完成，带有坐标校验的重试机制。"""
        max_retries = 3
        success = False
        
        for i in range(max_retries):
            start_pos = self._read_position(get_state)
            
            print(f">>> [后勤] 第 {i+1} 次尝试使用炉石（按键: {self.hearthstone_key}）...")
            # 先按一下 S 确保停止移动，防止移动中按炉石失败
            hw_ctrl.press('s')
            time.sleep(0.5)
            hw_ctrl.press(self.hearthstone_key)
            
            # 等待施法过程
            print(f">>> [后勤] 正在等待炉石 {self.hearthstone_cast_sec}s...")
            time.sleep(float(self.hearthstone_cast_sec))
            
            if get_state and start_pos is not None:
                for _ in range(5):
                    end_pos = self._read_position(get_state)
                    if end_pos is None:
                        time.sleep(0.5)
                        continue
                    dist = math.dist(start_pos, end_pos)
                    # 如果坐标发生了明显跳变（大于 2.0），证明回城成功
                    if dist > 2.0:
                        print(f">>> [后勤] 炉石回城成功！位置跳变距离: {dist:.2f}")
                        success = True
                        break
                    time.sleep(0.5)
                if success:
                    break
                print(f">>> [后勤] 炉石似乎失败（位置未变化），准备重试...")
            elif get_state:
                st_now = get_state()
                if st_now is None:
                    # 获取不到状态可能已经卡死或窗口关闭，默认成功以退出循环
                    success = True
                    break
                else:
                    success = True
                    break
            else:
                # 如果没法校验坐标，就只执行一次
                success = True
                break
        
        if not success:
            print(">>> [后勤] 警告：多次尝试炉石回城均未检测到位置跳变！")
            
        self.is_returning = False
        return success

    def return_home(self):
        """执行回城动作"""
        # 1. 停止当前巡逻
        # 2. 寻找安全点或直接使用炉石
        print(">>> 正在释放炉石...")
        hw_ctrl.press('7') # 假设炉石在 7 号键
        time.sleep(15) # 等待炉石施法
    
    def handle_town_visit(self, state, patrol):
        """回城流程：用 state 取当前坐标与朝向，调用 patrol.navigate_to_point 前往修理商；到达后按 F3 交互"""
        is_arrived = patrol.navigate_to_point(
                state, VENDOR_POS,
                arrival_threshold=VENDOR_ARRIVAL_THRESHOLD,
            )
        if is_arrived:
            print(">>> 到达修理商，执行交互宏")
            self._do_vendor_interact()
            self.is_returning = False

    def _do_vendor_interact(self):
        """执行与修理商/背包的交互按键。"""
        hw_ctrl.press(self.repair_target_key)
        time.sleep(0.5)
        hw_ctrl.press(self.repair_interact_key)
        time.sleep(float(self.repair_interact_wait_sec))

    def run_bag_full_mail_flow(self, get_state, patrol, stop_check=None, use_hearthstone=True):
        """
        包满邮寄第一版流程：
        炉石 -> 跑到邮箱路线终点 -> 交互打开邮箱 -> 按 MailboxCourier 宏键 -> 等待后停止。
        Python 不判断邮件是否发完，发送细节交给游戏内插件。
        """
        route_file = self._resolve_path(self.mailbox_route_file)
        if not route_file or not os.path.exists(route_file):
            print(f">>> [后勤-邮箱] 邮箱路线不存在，已停止: {route_file}")
            self.is_returning = False
            return False

        if callable(stop_check) and stop_check():
            self.is_returning = False
            return False

        if use_hearthstone:
            ok = self.use_hearthstone_and_stop(get_state=get_state)
            if not ok:
                print(">>> [后勤-邮箱] 炉石失败，未继续跑邮箱。")
                self.is_returning = False
                return False

        try:
            with open(route_file, "r", encoding="utf-8") as f:
                path = json.load(f)
        except Exception as exc:
            print(f">>> [后勤-邮箱] 邮箱路线读取失败: {exc}")
            self.is_returning = False
            return False

        if not path:
            print(f">>> [后勤-邮箱] 邮箱路线为空，已停止: {route_file}")
            self.is_returning = False
            return False

        print(f">>> [后勤-邮箱] 开始跑邮箱路线: {route_file}")
        old_enable_mount = getattr(patrol, "enable_mount", None)
        if old_enable_mount is not None:
            patrol.enable_mount = False
        try:
            ok = patrol.navigate_path(
                get_state,
                path,
                forward=True,
                arrival_threshold=VENDOR_ARRIVAL_THRESHOLD,
                snap_to_nearest=True,
            )
        finally:
            if old_enable_mount is not None:
                patrol.enable_mount = old_enable_mount
        if not ok:
            print(">>> [后勤-邮箱] 邮箱路线未完成，已停止。")
            self.is_returning = False
            return False

        if callable(stop_check) and stop_check():
            self.is_returning = False
            return False

        patrol.stop_all()
        print(f">>> [后勤-邮箱] 到达邮箱附近，按交互键: {self.mailbox_interact_key}")
        hw_ctrl.press(self.mailbox_interact_key)
        if not self._sleep_with_stop(self.mailbox_open_wait_sec, stop_check=stop_check):
            self.is_returning = False
            return False

        if self.mail_recipient_key:
            print(f">>> [后勤-邮箱] 触发 MailboxCourier 收件人宏: {self.mail_recipient_key}")
            hw_ctrl.press(self.mail_recipient_key)
            if not self._sleep_with_stop(0.5, stop_check=stop_check):
                self.is_returning = False
                return False

        print(f">>> [后勤-邮箱] 触发 MailboxCourier 发送宏: {self.mail_send_key}")
        hw_ctrl.press(self.mail_send_key)
        if not self._sleep_with_stop(self.mail_send_wait_sec, stop_check=stop_check):
            self.is_returning = False
            return False

        print(">>> [后勤-邮箱] 邮寄宏已触发，流程结束。")
        self.is_returning = False
        return True

    def run_repair_flow(self, get_state, patrol, stop_check=None, use_hearthstone=True):
        """
        耐久低修理流程：
        炉石 -> 从修理路线最近点接入 -> 到达 NPC -> 按目标键 -> 按交互/修理键。
        """
        route_file = self._resolve_path(self.route_file)
        if not route_file or not os.path.exists(route_file):
            print(f">>> [后勤-修理] 修理路线不存在，已停止: {route_file}")
            self.is_returning = False
            return False

        if callable(stop_check) and stop_check():
            self.is_returning = False
            return False

        if use_hearthstone:
            ok = self.use_hearthstone_and_stop(get_state=get_state)
            if not ok:
                print(">>> [后勤-修理] 炉石失败，未继续跑修理路线。")
                self.is_returning = False
                return False

        try:
            with open(route_file, "r", encoding="utf-8") as f:
                path = json.load(f)
        except Exception as exc:
            print(f">>> [后勤-修理] 修理路线读取失败: {exc}")
            self.is_returning = False
            return False

        if not path:
            print(f">>> [后勤-修理] 修理路线为空，已停止: {route_file}")
            self.is_returning = False
            return False

        print(f">>> [后勤-修理] 开始跑修理路线: {route_file}")
        ok = patrol.navigate_path(
            get_state,
            path,
            forward=True,
            arrival_threshold=VENDOR_ARRIVAL_THRESHOLD,
            snap_to_nearest=True,
        )
        if not ok:
            print(">>> [后勤-修理] 修理路线未完成，已停止。")
            self.is_returning = False
            return False

        if callable(stop_check) and stop_check():
            self.is_returning = False
            return False

        patrol.stop_all()
        print(f">>> [后勤-修理] 到达 NPC，按目标键: {self.repair_target_key}")
        hw_ctrl.press(self.repair_target_key)
        if not self._sleep_with_stop(0.5, stop_check=stop_check):
            self.is_returning = False
            return False

        print(f">>> [后勤-修理] 按修理交互键: {self.repair_interact_key}")
        hw_ctrl.press(self.repair_interact_key)
        if not self._sleep_with_stop(self.repair_interact_wait_sec, stop_check=stop_check):
            self.is_returning = False
            return False

        print(">>> [后勤-修理] 修理流程结束。")
        self.is_returning = False
        return True

    def run_pending_tasks(self, tasks, get_state, patrol, stop_check=None):
        completed = []
        already_homed = False
        for task in tasks:
            if callable(stop_check) and stop_check():
                self.is_returning = False
                return False, completed

            if task == "mail":
                ok = self.run_bag_full_mail_flow(
                    get_state,
                    patrol,
                    stop_check=stop_check,
                    use_hearthstone=not already_homed,
                )
                if not ok:
                    return False, completed
                already_homed = True
                completed.append(task)
                continue

            if task == "repair":
                ok = self.run_repair_flow(
                    get_state,
                    patrol,
                    stop_check=stop_check,
                    use_hearthstone=not already_homed,
                )
                if not ok:
                    return False, completed
                already_homed = True
                completed.append(task)
                continue

            if task == "hearthstone_stop":
                ok = self.use_hearthstone_and_stop(get_state=get_state)
                if not ok:
                    return False, completed
                already_homed = True
                completed.append(task)

        self.is_returning = False
        return True, completed

    def run_route1_round(self, get_state, patrol, route_file=None):
        """
        读取 route1.json 路径，先正向走完，执行交互（8、4），再反向走完，然后结束。
        get_state: 可调用对象，返回当前状态 dict（含 x, y, facing）。
        patrol: CoordinatePatrol 实例，用于 navigate_path。
        route_file: 路径 JSON 文件路径，默认使用 __init__ 中的 route_file。
        """
        route_file = route_file or self.route_file
        with open(route_file, "r", encoding="utf-8") as f:
            path = json.load(f)
        if not path:
            print(">>> [后勤] route1 为空，跳过")
            return
        print(">>> [后勤] 开始 route1 正向")
        ok = patrol.navigate_path(get_state, path, forward=True, arrival_threshold=VENDOR_ARRIVAL_THRESHOLD)
        if not ok:
            print(">>> [后勤] 正向未完成，中止")
            return
        print(">>> [后勤] 正向到达，执行交互")
        self._do_vendor_interact()
        print(">>> [后勤] 开始 route1 反向")
        ok = patrol.navigate_path(get_state, path, forward=False, arrival_threshold=VENDOR_ARRIVAL_THRESHOLD)
        if not ok:
            print(">>> [后勤] 反向未完成")
            return
        print(">>> [后勤] route1 往返结束")
