from typing import List, Optional

import allure
from playwright.sync_api import Page, expect

from zhyy.test_case.Resource.UI.base_page import BasePage


class SmartManagementLoginPage(BasePage):
    """智慧运营登录页。"""

    # TODO：如项目已有页面侦察结果，请替换为真实 selector
    USERNAME_INPUT_SELECTORS = [
        'input[name="username"]',
        'input[id="username"]',
        'input[placeholder*="用户名"]',
        'input[placeholder*="账号"]',
        'input[type="text"]',
    ]

    PASSWORD_INPUT_SELECTORS = [
        'input[name="password"]',
        'input[id="password"]',
        'input[placeholder*="密码"]',
        'input[type="password"]',
    ]

    LOGIN_BUTTON_SELECTORS = [
        'button:has-text("登录")',
        'button:has-text("登 录")',
        'button[type="submit"]',
    ]

    def is_login_page(self) -> bool:
        url = self.page.url.lower()
        if "login" in url:
            return True
        try:
            self.first_visible_locator(self.PASSWORD_INPUT_SELECTORS, timeout=2000, description="密码输入框")
            return True
        except Exception:
            return False

    @allure.step("登录智慧运营系统")
    def login_if_required(self, username: str, password: str):
        if not self.is_login_page():
            return

        self.fill_first_visible(self.USERNAME_INPUT_SELECTORS, username, description="用户名输入框")
        self.fill_first_visible(self.PASSWORD_INPUT_SELECTORS, password, description="密码输入框")
        self.click_first_visible(self.LOGIN_BUTTON_SELECTORS, description="登录按钮")
        self.wait_for_page_ready()


class ContractManagementPage(BasePage):
    """合同管理页面：封装合同编号模糊查询能力。"""

    PAGE_URL = "https://smart-management-web-st.best-envision.com/SZPurchase/PurchaseManage/PurchaseOrderManage"

    # TODO：页面侦察结果为空，以下为基于业务语义的候选定位；落地时建议替换为真实稳定 selector/data-testid
    CONTRACT_MENU_SELECTORS = [
        'a:has-text("合同管理")',
        'li:has-text("合同管理")',
        'span:has-text("合同管理")',
        'div:has-text("合同管理")',
    ]

    CONTRACT_NO_INPUT_SELECTORS = [
        # Ant Design 常见结构：label 为“合同编号”后紧邻输入框
        'xpath=//*[normalize-space()="合同编号"]/following::input[1]',
        'input[placeholder*="合同编号"]',
        'input[name*="contract"]',
        'input[id*="contract"]',
        # TODO：补充页面侦察得到的合同编号输入框 selector
    ]

    QUERY_BUTTON_SELECTORS = [
        'button:has-text("查询")',
        'button:has-text("搜索")',
        # TODO：补充页面侦察得到的查询按钮 selector
    ]

    RESET_BUTTON_SELECTORS = [
        'button:has-text("重置")',
        'button:has-text("清空")',
    ]

    TABLE_ROW_SELECTORS = [
        # Ant Design 表格常见结构
        '.ant-table-tbody > tr:not(.ant-table-placeholder)',
        'table tbody tr',
        # TODO：补充页面侦察得到的列表行 selector
    ]

    TABLE_HEADER_CELL_SELECTORS = [
        '.ant-table-thead th',
        'table thead th',
        # TODO：补充页面侦察得到的表头 selector
    ]

    EMPTY_SELECTORS = [
        '.ant-empty',
        'text=暂无数据',
        'text=无数据',
    ]

    def __init__(self, page: Page):
        super().__init__(page)
        self.login_page = SmartManagementLoginPage(page)

    @allure.step("打开合同管理页面")
    def open(self, username: str, password: str):
        self.goto(self.PAGE_URL)
        self.login_page.login_if_required(username, password)

        # 登录后如回到首页，则再次进入目标页面
        if "PurchaseOrderManage" not in self.page.url:
            self.goto(self.PAGE_URL)
            self.wait_for_page_ready()

        self.ensure_contract_management_page_loaded()

    @allure.step("确认合同管理页面加载完成")
    def ensure_contract_management_page_loaded(self):
        """
        优先确认页面 URL，其次确认页面上存在合同相关筛选项。
        """
        if "PurchaseOrderManage" in self.page.url:
            return

        try:
            self.first_visible_locator(self.CONTRACT_MENU_SELECTORS, timeout=5000, description="合同管理菜单")
            return
        except Exception as exc:
            raise AssertionError(f"合同管理页面未成功加载，当前 URL={self.page.url}") from exc

    @allure.step("点击合同管理菜单")
    def click_contract_menu_if_visible(self):
        """
        用户步骤要求“找到合同菜单”；若当前已在目标页面，则不强制点击。
        """
        if "PurchaseOrderManage" in self.page.url:
            return

        self.click_first_visible(self.CONTRACT_MENU_SELECTORS, timeout=5000, description="合同管理菜单")
        self.wait_for_page_ready()

    @allure.step("输入合同编号查询条件：{contract_no_keyword}")
    def input_contract_no_keyword(self, contract_no_keyword: str):
        try:
            self.page.get_by_label("合同编号", exact=False).fill(contract_no_keyword)
        except Exception:
            self.fill_first_visible(
                self.CONTRACT_NO_INPUT_SELECTORS,
                contract_no_keyword,
                description="合同编号搜索框",
            )

    @allure.step("点击查询按钮")
    def click_query(self):
        try:
            self.page.get_by_role("button", name="查询").click()
            self.wait_for_page_ready()
        except Exception:
            self.click_first_visible(self.QUERY_BUTTON_SELECTORS, description="查询按钮")

        # 等待列表刷新完成
        self.page.wait_for_timeout(1000)

    @allure.step("按合同编号模糊查询：{contract_no_keyword}")
    def search_by_contract_no(self, contract_no_keyword: str):
        self.input_contract_no_keyword(contract_no_keyword)
        self.click_query()

    def _get_contract_no_column_index(self) -> Optional[int]:
        """
        根据表头文本动态识别“合同编号”列索引。
        识别失败时返回 None，断言时退化为行文本包含校验。
        """
        for selector in self.TABLE_HEADER_CELL_SELECTORS:
            headers = self.page.locator(selector)
            try:
                count = headers.count()
            except Exception:
                continue

            for index in range(count):
                text = headers.nth(index).inner_text(timeout=2000).strip()
                if "合同编号" in text:
                    return index
        return None

    def get_result_rows_text(self) -> List[str]:
        for selector in self.TABLE_ROW_SELECTORS:
            rows = self.page.locator(selector)
            try:
                count = rows.count()
            except Exception:
                continue

            row_texts = []
            for index in range(count):
                row = rows.nth(index)
                text = row.inner_text(timeout=3000).strip()
                if text:
                    row_texts.append(text)
            if row_texts:
                return row_texts
        return []

    def get_result_contract_numbers(self) -> List[str]:
        """
        返回结果列表中的合同编号列文本。
        若无法识别合同编号列，则返回空列表，由上层断言决定是否退化为行文本校验。
        """
        column_index = self._get_contract_no_column_index()
        if column_index is None:
            return []

        for selector in self.TABLE_ROW_SELECTORS:
            rows = self.page.locator(selector)
            try:
                row_count = rows.count()
            except Exception:
                continue

            contract_numbers = []
            for row_index in range(row_count):
                row = rows.nth(row_index)
                cells = row.locator("td")
                if cells.count() > column_index:
                    contract_no = cells.nth(column_index).inner_text(timeout=3000).strip()
                    if contract_no:
                        contract_numbers.append(contract_no)

            if contract_numbers:
                return contract_numbers

        return []

    def is_empty_result(self) -> bool:
        for selector in self.EMPTY_SELECTORS:
            try:
                if self.page.locator(selector).first.is_visible(timeout=2000):
                    return True
            except Exception:
                continue
        return False

    @allure.step("断言列表仅展示合同编号模糊匹配：{contract_no_keyword}")
    def assert_contract_no_fuzzy_match(self, contract_no_keyword: str):
        """
        优先按“合同编号”列断言；
        如因页面侦察缺失无法识别列，则退化为断言每行文本均包含查询关键字。
        """
        expect(self.page.locator("body")).to_be_visible()

        contract_numbers = self.get_result_contract_numbers()
        if contract_numbers:
            assert all(contract_no_keyword in item for item in contract_numbers), (
                f"存在合同编号不匹配查询条件：keyword={contract_no_keyword}, "
                f"contract_numbers={contract_numbers}"
            )
            return

        row_texts = self.get_result_rows_text()
        if row_texts:
            assert all(contract_no_keyword in row_text for row_text in row_texts), (
                f"无法识别合同编号列，已退化为行文本校验；存在行不包含查询条件："
                f"keyword={contract_no_keyword}, rows={row_texts}"
            )
            return

        assert self.is_empty_result(), "查询后列表无数据且未识别到空数据提示，请检查表格 selector 或查询条件"
