"""常规战斗操作 — 多节点地图战斗。

涉及跨页面操作: 主页面 → 地图页面(出征面板) → 选章节/地图 → 出征准备 → 战斗 → 地图页面。

旧代码参考: ``fight/normal_fight.py`` (NormalFightPlan)
"""

from __future__ import annotations

import time
from typing import TYPE_CHECKING

from autowsgr.combat import CombatMode, CombatPlan, CombatResult
from autowsgr.combat.engine import run_combat
from autowsgr.infra.logger import get_logger
from autowsgr.ops import goto_page
from autowsgr.types import ConditionFlag, PageName, RepairMode, ShipDamageState
from autowsgr.ui import BattlePreparationPage, MapPage, MapPanel, RepairStrategy


if TYPE_CHECKING:
    from autowsgr.context import GameContext
    from autowsgr.context.ship import Ship


_log = get_logger('ops')


class NormalFightRunner:
    """常规战斗执行器。"""

    def __init__(
        self,
        ctx: GameContext,
        plan: CombatPlan,
        fleet_id: int | None = None,
        fleet: list[str] | None = None,
    ) -> None:
        self._ctx = ctx
        self._ctrl = ctx.ctrl
        self._plan = plan
        self._fleet_id = fleet_id if fleet_id is not None else plan.fleet_id
        self._fleet = fleet if fleet is not None else plan.fleet

        # 从 config 读取拆船配置
        self._dock_full_destroy = ctx.config.dock_full_destroy
        self._destroy_ship_types = ctx.config.destroy_ship_types or None

        # 确保 plan 模式是 NORMAL
        if plan.mode != CombatMode.NORMAL:
            _log.warning(
                '[OPS] NormalFightRunner 收到非 NORMAL 模式的计划: {}, 已修正',
                plan.mode,
            )
            plan.mode = CombatMode.NORMAL

        self._results: list[CombatResult] = []
        self._loot_count: int | None = None
        self._ship_acquired_count: int | None = None
        self._fleet_ships: list[Ship] | None = None

    # ── 公共接口 ──

    def run(self) -> CombatResult:
        """执行一次完整的常规战。

        1. 进入地图
        2. 出征准备
        3. 战斗
        4. 处理结果

        Returns
        -------
        CombatResult
        """
        _log.info(
            '[OPS] 常规战: {}-{} ({})',
            self._plan.chapter,
            self._plan.map_id,
            self._plan.name,
            self._fleet_id,
            self._fleet,
        )

        # 1. 进入战斗地图
        self._enter_fight()

        # 2. 出征准备
        ship_stats = self._prepare_for_battle()

        # 同步战前信息到上下文
        self._ctx.sync_before_combat(
            self._fleet_id,
            self._fleet_ships,
            loot_count=self._loot_count,
            ship_acquired_count=self._ship_acquired_count,
        )

        # 3. 执行战斗
        result = self._do_combat(ship_stats)

        # 赋值出征面板识别到的今日获取数量和舰队信息
        result.loot_count = self._loot_count
        result.ship_acquired_count = self._ship_acquired_count
        result.fleet = self._fleet_ships

        # 同步战后信息到上下文
        self._ctx.sync_after_combat(self._fleet_id, result)

        # 4. 处理结果
        self._handle_result(result)

        return result

    def run_for_times(
        self,
        times: int,
        *,
        gap: float = 0.0,
        **kwargs,
    ) -> list[CombatResult]:
        """重复执行常规战。

        Parameters
        ----------
        times:
            重复次数。
        gap:
            每次战斗之间的间隔 (秒)。

        Returns
        -------
        list[CombatResult]
        """
        _log.info('[OPS] 常规战连续执行 {} 次', times)
        self._results = []

        for i in range(times):
            _log.info('[OPS] 常规战第 {}/{} 次', i + 1, times)
            result = self.run(**kwargs)
            self._results.append(result)

            if result.flag == ConditionFlag.DOCK_FULL:
                _log.warning('[OPS] 船坞已满, 停止')
                break

            if gap > 0 and i < times - 1:
                time.sleep(gap)

        _log.info(
            '[OPS] 常规战完成: {} 次 (成功 {} 次)',
            len(self._results),
            sum(1 for r in self._results if r.flag == ConditionFlag.OPERATION_SUCCESS),
        )
        return self._results

    def run_for_times_condition(
        self,
        times: int,
        last_point: str,
        *,
        result: str = 'S',
        insist_time: float = 900.0,
    ) -> list[CombatResult] | bool:
        """有战果要求的多次运行。

        循环执行战斗直到满足预设条件。如果最后一个节点的战果未达到要求，
        此次战斗不计入次数。超过指定时间仍未完成则返回 False。

        Parameters
        ----------
        times:
            需要完成的次数。
        last_point:
            最后一个节点（如 "A"、"B" 等）。
        result:
            战果要求（"S"、"A"、"B"、"C"、"D"、"SS"），默认为 "S"。
        insist_time:
            超时时间（秒）。如果超过这个时间仍未完成则返回 False，默认为 900 秒。

        Returns
        -------
        list[CombatResult] | bool
            成功时返回战斗结果列表，超时返回 False。

        Raises
        ------
        ValueError:
            result 或 last_point 值不合法。
        """
        if result.upper() not in ['SS', 'S', 'A', 'B', 'C', 'D']:
            raise ValueError(
                f"战果要求: {result}, 不合法, 应为 'SS','S','A','B','C' 或 'D'",
            )
        if (
            len(last_point) != 1
            or ord(last_point.upper()) > ord('Z')
            or ord(last_point.upper()) < ord('A')
        ):
            raise ValueError(f'最后一个节点: {last_point}, 不合法, 应为A到Z的字母')

        result_list = ['D', 'C', 'B', 'A', 'S', 'SS']
        target_result_index = result_list.index(result.upper())
        start_time = time.time()
        self._results = []

        while times > 0:
            _log.info('[OPS] 条件战斗，剩余次数：{}', times)
            r = self.run()
            self._results.append(r)

            if r.flag == ConditionFlag.DOCK_FULL:
                _log.error('[OPS] 条件战斗，船坞已满，无法继续')
                return self._results

            # 获取最后一个节点的战果
            fight_results = r.fight_results
            if not fight_results:
                _log.warning('[OPS] 条件战斗，未获取到有效战果')
                continue

            last_result = fight_results[-1]
            fight_result_index = result_list.index(last_result.grade)
            # 检查是否满足条件
            finish = (
                last_result.node == last_point.upper() and fight_result_index >= target_result_index
            )

            if not finish:
                _log.info(
                    '[OPS] 不满足预设条件 (节点={}, 战果={}), 此次战斗不计入次数，剩余次数: {}',
                    last_result.node,
                    last_result.grade,
                    times,
                )
                if time.time() - start_time > insist_time:
                    return False
            else:
                start_time = time.time()
                times -= 1
                _log.info(
                    '[OPS] 完成了一次满足预设条件的战斗，剩余次数: {}',
                    times,
                )

        return self._results

    # ── 进入地图 ──

    def _enter_fight(self) -> None:
        """导航到目标地图并进入。"""
        goto_page(self._ctx, PageName.MAP)
        map_page = MapPage(self._ctx)

        # 在出征面板读取今日已获取数量
        map_page.ensure_panel(MapPanel.SORTIE)
        time.sleep(0.25)
        try:
            counts = map_page.get_loot_and_ship_count()
            self._loot_count = counts.loot
            self._ship_acquired_count = counts.ship
        except RuntimeError:
            _log.warning('[OPS] 无法读取今日获取数量 (OCR 不可用)')

        map_page.enter_sortie(self._plan.chapter, self._plan.map_id)

    # ── 出征准备 ──

    def _prepare_for_battle(self) -> list[ShipDamageState]:
        """出征准备: 舰队选择、修理、检测血量。

        Returns
        -------
        list[int]
            战前血量状态。
        """
        time.sleep(1.0)
        page = BattlePreparationPage(self._ctx)

        # 选择舰队
        page.select_fleet(self._fleet_id)
        time.sleep(0.5)

        # 换船 (如果指定了舰船列表)
        if self._fleet is not None:
            page.change_fleet(
                self._fleet_id,
                self._fleet,
            )
            time.sleep(0.5)

        # 补给
        page.apply_supply()
        time.sleep(0.3)

        # 修理策略
        repair_modes = self._plan.repair_mode
        if isinstance(repair_modes, list):
            min_mode = min(m.value for m in repair_modes)
        else:
            min_mode = repair_modes.value

        if min_mode <= RepairMode.moderate_damage.value:
            page.apply_repair(RepairStrategy.MODERATE)
        elif min_mode <= RepairMode.severe_damage.value:
            page.apply_repair(RepairStrategy.SEVERE)

        # 检测战前舰队信息 (血量 + 等级)
        fleet_info = page.detect_fleet_info()
        ship_stats = [fleet_info.ship_damage.get(i, ShipDamageState.NORMAL) for i in range(6)]
        self._fleet_ships = fleet_info.to_ships(self._fleet)

        # 出征
        page.start_battle()
        time.sleep(1.0)

        return ship_stats

    # ── 战斗 ──

    def _do_combat(self, ship_stats: list[ShipDamageState]) -> CombatResult:
        """构建 CombatEngine 并执行战斗。"""
        return run_combat(
            self._ctx,
            self._plan,
            ship_stats=ship_stats,
        )

    # ── 结果处理 ──

    def _handle_result(self, result: CombatResult) -> None:
        """处理战斗结果。

        DOCK_FULL 由战斗引擎在 START_FIGHT → DOCK_FULL 转移中检测并返回，
        此处根据配置决定自动解装或保持标志交由上层处理。
        """
        if result.flag == ConditionFlag.DOCK_FULL:
            self._handle_dock_full(result)
            return
        _log.info('[OPS] 常规战结果: {}', result.flag.value)

    def _handle_dock_full(self, result: CombatResult) -> None:
        """船坞已满: 按配置自动解装并重试，或保持 DOCK_FULL 标志。"""
        if self._dock_full_destroy:
            from autowsgr.ops.destroy import destroy_ships

            _log.warning('[OPS] 船坞已满，执行自动解装')
            # 点击弹窗确认按钮 (legacy 坐标)
            self._ctrl.click(0.38, 0.565)
            destroy_ships(
                self._ctx,
                ship_types=self._destroy_ship_types,
            )
            # 解装后标记为成功 (调用方可根据需要重试出征)
            result.flag = ConditionFlag.OPERATION_SUCCESS
            return

        _log.warning('[OPS] 船坞已满, 未开启自动解装')
        # result.flag 保持 DOCK_FULL, 由 run_for_times 终止循环


# ═══════════════════════════════════════════════════════════════════════════════
# 便捷函数
# ═══════════════════════════════════════════════════════════════════════════════


def get_normal_fight_plan(yaml_path: str) -> CombatPlan:
    """从 YAML 文件加载常规战计划。"""
    from autowsgr.infra.file_utils import resolve_plan_path

    resolved = resolve_plan_path(yaml_path, category='normal_fight')
    return CombatPlan.from_yaml(resolved)


def run_normal_fight(
    ctx: GameContext,
    plan: CombatPlan,
    *,
    times: int = 1,
    gap: float = 0.0,
    fleet_id: int | None = None,
    fleet: list[str] | None = None,
) -> list[CombatResult]:
    """执行常规战的便捷函数。"""
    runner = NormalFightRunner(
        ctx,
        plan,
        fleet_id=fleet_id,
        fleet=fleet,
    )
    return runner.run_for_times(times, gap=gap)


def run_normal_fight_from_yaml(
    ctx: GameContext,
    yaml_path: str,
    *,
    times: int = 1,
    fleet_id: int | None = None,
    fleet: list[str] | None = None,
) -> list[CombatResult]:
    """从 YAML 文件加载计划并执行常规战。

    *yaml_path* 支持以下格式:

    - 绝对路径 / 相对路径: 直接加载。
    - 策略名称 (如 ``"7-4千伪"``): 自动在 ``autowsgr/data/plan/normal_fight/``
      包数据目录中查找，可省略 ``.yaml`` 后缀。
    """
    plan = get_normal_fight_plan(yaml_path)
    return run_normal_fight(ctx, plan, times=times, fleet_id=fleet_id, fleet=fleet)
