from __future__ import annotations

from collections.abc import Iterable

from playwright.sync_api import Locator, expect

from e2e.pages.admin_page import AdminPage


class SaasSubscriptionAdminPage(AdminPage):
    def open_application_list_new(self) -> None:
        self.maybe_click_text(["应用管理"], timeout=3000)
        self.click_first_visible(
            [
                "text=应用列表新",
                "text=应用列表",
                ".ant-menu-item:has-text('应用列表新')",
                ".ant-menu-item:has-text('应用列表')",
            ],
            timeout=5000,
        )
        self.page.wait_for_load_state("domcontentloaded")
        self.page.wait_for_timeout(1500)

    def search_app_by_code(self, app_code: str) -> Locator:
        self.open_application_list_new()
        self.fill_first_visible(
            [
                "input[placeholder*='应用编码']",
                "input[placeholder*='应用ID']",
                "input[placeholder*='请输入应用编码']",
                "input[placeholder*='搜索应用编码']",
            ],
            app_code,
            timeout=5000,
        )
        self.click_first_visible(
            [
                "button:has-text('查询')",
                "button:has-text('搜索')",
                "text=查询",
            ],
            timeout=3000,
        )
        self.page.wait_for_timeout(1500)
        row = self.find_table_row(app_code)
        expect(row).to_be_visible(timeout=5000)
        return row

    def open_app_version_management(self, app_code: str) -> None:
        row = self.search_app_by_code(app_code)
        self.click_row_action(row, ["版本管理", "版本"])
        self.page.wait_for_timeout(1500)
        expect(self.page.get_by_text("应用版本管理", exact=False).first).to_be_visible(timeout=5000)

    def create_app_version(self, version_name: str, description: str) -> None:
        self.click_first_visible(
            [
                ".ant-modal button:has-text('新增版本')",
                ".ant-drawer button:has-text('新增版本')",
                "button:has-text('新增版本')",
            ],
            timeout=5000,
        )
        self.page.wait_for_timeout(800)
        modal = self.page.locator(".ant-modal").filter(has_text="新增应用版本").last
        expect(modal).to_be_visible(timeout=5000)

        self.fill_and_verify(
            modal.locator("input[placeholder*='版本名称'], input[placeholder*='请输入版本名称']").first,
            version_name,
            timeout=5000,
        )

        description_input = modal.locator(
            "textarea[placeholder*='版本说明'], textarea[placeholder*='版本描述'], textarea"
        ).first
        expect(description_input).to_be_visible(timeout=3000)
        description_input.fill(description)

        self.ensure_radio_or_select_value_in_container(modal, ["启用", "正常"])
        self.click_ant_modal_button_by_span_text(["保 存", "保存", "确 定", "确定"], timeout=5000)
        self.page.wait_for_timeout(2500)

    def edit_app_version(self, old_version_name: str, new_version_name: str, description: str) -> None:
        self.select_app_version(old_version_name)
        self.click_first_visible(
            [
                ".ant-modal button:has-text('编辑版本')",
                ".ant-drawer button:has-text('编辑版本')",
                "button:has-text('编辑版本')",
            ],
            timeout=5000,
        )
        self.page.wait_for_timeout(800)
        modal = self.page.locator(".ant-modal").filter(has_text="编辑应用版本").last
        expect(modal).to_be_visible(timeout=5000)

        self.fill_and_verify(
            modal.locator("input[placeholder*='版本名称'], input[placeholder*='请输入版本名称']").first,
            new_version_name,
            timeout=5000,
        )

        description_input = modal.locator(
            "textarea[placeholder*='版本说明'], textarea[placeholder*='版本描述'], textarea"
        ).first
        expect(description_input).to_be_visible(timeout=3000)
        description_input.fill(description)

        self.ensure_radio_or_select_value_in_container(modal, ["启用", "正常"])
        self.click_ant_modal_button_by_span_text(["保 存", "保存", "确 定", "确定"], timeout=5000)
        self.page.wait_for_timeout(2500)

    def search_version_resource(self, resource_name: str) -> None:
        self._fill_version_resource_keyword(resource_name)
        self._click_version_resource_query()
        self.page.wait_for_timeout(1500)
        expect(self._version_resource_row(resource_name)).to_be_visible(timeout=8000)

    def ensure_version_resource_checked(self, resource_name: str, checked: bool = True) -> None:
        self.search_version_resource(resource_name)
        if self.is_version_resource_checked(resource_name) == checked:
            return

        clicked = self.page.evaluate(
            """([resourceName]) => {
              const modal = Array.from(document.querySelectorAll('.ant-modal'))
                .reverse()
                .find(element => element.offsetParent !== null) || document.body;
              const treeNodes = Array.from(modal.querySelectorAll('.ant-tree-treenode'))
                .filter(node => node.offsetParent !== null && node.innerText.includes(resourceName));
              const treeNode = treeNodes[0];
              if (treeNode) {
                const checkbox = treeNode.querySelector('.ant-tree-checkbox[role="checkbox"], .ant-tree-checkbox');
                if (!checkbox) return false;
                checkbox.click();
                return true;
              }
              const rows = Array.from(modal.querySelectorAll('.ant-table-row, tr'))
                .filter(row => row.offsetParent !== null && row.innerText.includes(resourceName));
              const row = rows[0];
              if (!row) return false;
              const rowKey = row.getAttribute('data-row-key');
              const allRows = Array.from(modal.querySelectorAll('.ant-table-row, tr'))
                .filter(item => item.offsetParent !== null);
              const sameKeyRows = rowKey
                ? allRows.filter(item => item.getAttribute('data-row-key') === rowKey)
                : [];
              const textRows = allRows.filter(item => item.innerText.includes(resourceName));
              const rowIndex = textRows.indexOf(row);
              const bodyRows = allRows.filter(item => item.closest('.ant-table-tbody, tbody'));
              const indexRows = rowIndex >= 0 ? bodyRows.filter((_, index) => index === rowIndex) : [];
              const candidates = [row, ...sameKeyRows, ...indexRows];
              const checkbox = candidates
                .map(item => item.querySelector('input[type="checkbox"], .ant-checkbox-input, .ant-checkbox, [role="checkbox"]'))
                .find(Boolean);
              if (!checkbox) return false;
              const clickable = checkbox.closest('label, .ant-checkbox-wrapper, .ant-checkbox') || checkbox;
              clickable.click();
              return true;
            }""",
            [resource_name],
        )
        assert clicked, f"Could not click resource checkbox: {resource_name}"
        self.page.wait_for_timeout(800)
        assert self.is_version_resource_checked(resource_name) == checked, (
            f"Resource checkbox state mismatch: {resource_name}, expected={checked}"
        )

    def save_current_version_resources(self) -> None:
        self.click_first_visible(
            [
                "button:has-text('保存当前版本资源')",
                "button:has-text('淇濆瓨褰撳墠鐗堟湰璧勬簮')",
                "button:has-text('保存')",
                "button:has-text('淇濆瓨')",
            ],
            timeout=5000,
        )
        self.page.wait_for_timeout(2500)

    def select_all_filtered_version_resources(self) -> None:
        clicked = self.page.evaluate(
            """() => {
              const modal = Array.from(document.querySelectorAll('.ant-modal'))
                .reverse()
                .find(element => element.offsetParent !== null) || document.body;
              const candidates = Array.from(modal.querySelectorAll('label, .ant-checkbox-wrapper, div, span'))
                .filter(element => element.offsetParent !== null && /全选当前筛选结果|鍏ㄩ€夊綋鍓嶇瓫閫夌粨鏋?/.test(element.innerText || element.textContent || ''));
              const container = candidates
                .map(element => element.closest('label, .ant-checkbox-wrapper') || element)
                .find(Boolean);
              if (!container) return false;
              const input = container.querySelector('input[type="checkbox"], .ant-checkbox-input');
              const checkbox = container.querySelector('.ant-checkbox, [role="checkbox"]') || input;
              if (!checkbox) return false;
              const className = String((checkbox.closest('.ant-checkbox') || checkbox).className || '');
              const checked = !!(input && input.checked) || className.includes('ant-checkbox-checked');
              if (!checked) {
                const clickable = checkbox.closest('label, .ant-checkbox-wrapper, .ant-checkbox') || checkbox;
                clickable.click();
              }
              return true;
            }"""
        )
        assert clicked, "Could not select all filtered version resources"
        self.page.wait_for_timeout(800)

    def assert_version_resource_checked(self, resource_name: str, checked: bool = True) -> None:
        self.search_version_resource(resource_name)
        actual = self.is_version_resource_checked(resource_name)
        assert actual == checked, (
            f"Resource binding state mismatch: {resource_name}, expected={checked}, actual={actual}"
        )

    def is_version_resource_checked(self, resource_name: str) -> bool:
        self._version_resource_row(resource_name).wait_for(state="visible", timeout=8000)
        state = self.page.evaluate(
            """([resourceName]) => {
              const modal = Array.from(document.querySelectorAll('.ant-modal'))
                .reverse()
                .find(element => element.offsetParent !== null) || document.body;
              const treeNodes = Array.from(modal.querySelectorAll('.ant-tree-treenode'))
                .filter(node => node.offsetParent !== null && node.innerText.includes(resourceName));
              const treeNode = treeNodes[0];
              if (treeNode) {
                const checkbox = treeNode.querySelector('.ant-tree-checkbox[role="checkbox"], .ant-tree-checkbox');
                if (!checkbox) return null;
                const className = String(checkbox.className || '');
                const ariaChecked = checkbox.getAttribute('aria-checked');
                return className.includes('ant-tree-checkbox-checked') || ariaChecked === 'true';
              }
              const rows = Array.from(modal.querySelectorAll('.ant-table-row, tr'))
                .filter(row => row.offsetParent !== null && row.innerText.includes(resourceName));
              const row = rows[0];
              if (!row) return null;
              const rowKey = row.getAttribute('data-row-key');
              const allRows = Array.from(modal.querySelectorAll('.ant-table-row, tr'))
                .filter(item => item.offsetParent !== null);
              const sameKeyRows = rowKey
                ? allRows.filter(item => item.getAttribute('data-row-key') === rowKey)
                : [];
              const textRows = allRows.filter(item => item.innerText.includes(resourceName));
              const rowIndex = textRows.indexOf(row);
              const bodyRows = allRows.filter(item => item.closest('.ant-table-tbody, tbody'));
              const indexRows = rowIndex >= 0 ? bodyRows.filter((_, index) => index === rowIndex) : [];
              const candidates = [row, ...sameKeyRows, ...indexRows];
              const input = candidates.map(item => item.querySelector('input[type="checkbox"]')).find(Boolean);
              const checkbox = candidates
                .map(item => item.querySelector('.ant-checkbox, .ant-checkbox-wrapper, [role="checkbox"]'))
                .find(Boolean);
              if (input) return !!input.checked;
              if (checkbox) {
                const className = String(checkbox.className || '');
                const ariaChecked = checkbox.getAttribute('aria-checked');
                return className.includes('ant-checkbox-checked') || ariaChecked === 'true';
              }
              return null;
            }""",
            [resource_name],
        )
        assert state is not None, f"Could not read resource checkbox state: {resource_name}"
        return bool(state)

    def assert_version_visible(self, version_name: str) -> None:
        expect(self.page.get_by_text(version_name, exact=False).first).to_be_visible(timeout=8000)

    def select_app_version(self, version_name: str) -> None:
        version_card = self.page.locator(".ant-modal button", has_text=version_name).first
        if not version_card.count():
            version_card = self.page.get_by_text(version_name, exact=False).first
        expect(version_card).to_be_visible(timeout=8000)
        version_card.click()
        self.page.wait_for_timeout(800)

    def reopen_app_version_management_and_assert_version(self, app_code: str, version_name: str) -> None:
        self.close_visible_modal_or_drawer()
        self.open_app_version_management(app_code)
        self.assert_version_visible(version_name)

    def open_operation_log(self) -> None:
        self.close_all_visible_modals_or_drawers()
        self.maybe_click_text(["产品管理"], timeout=3000)
        self.click_first_visible(
            [
                "text=操作日志",
                ".ant-menu-item:has-text('操作日志')",
                "li:has-text('操作日志')",
            ],
            timeout=5000,
        )
        self.page.wait_for_timeout(1500)

    def assert_operation_log(self, target_type: str, target_code: str, operation_info: str) -> None:
        self.open_operation_log()
        self.select_filter_value("目标类型", target_type)
        self.fill_first_visible(
            [
                "input[placeholder*='目标编码']",
                "input[placeholder*='请输入目标编码']",
            ],
            target_code,
            timeout=5000,
        )
        self.click_first_visible(
            [
                "button:has-text('查询')",
                "button:has-text('搜索')",
            ],
            timeout=3000,
        )
        self.page.wait_for_timeout(1500)
        row = self.find_latest_operation_log_row(target_code, operation_info)
        row_text = row.inner_text(timeout=1000)
        assert target_code in row_text, f"操作日志目标编码不匹配，期望 {target_code}，实际行：{row_text}"
        assert "成功" in row_text, f"操作日志未显示成功状态，实际行：{row_text}"
        self.open_operation_log_detail(row)
        self.assert_operation_log_detail(target_code, operation_info)

    def ensure_modal_radio_or_select_value(self, labels: Iterable[str]) -> None:
        for label in labels:
            radio = self.page.locator(".ant-modal label", has_text=label).first
            if radio.count():
                try:
                    radio.click(timeout=1000)
                    return
                except Exception:
                    pass
            option = self.page.get_by_text(label, exact=True).first
            if option.count():
                try:
                    option.click(timeout=1000)
                    return
                except Exception:
                    pass

    def ensure_radio_or_select_value_in_container(self, container: Locator, labels: Iterable[str]) -> None:
        for label in labels:
            radio = container.locator("label", has_text=label).first
            if radio.count():
                try:
                    radio.click(timeout=1000)
                    return
                except Exception:
                    pass
            option = container.get_by_text(label, exact=True).first
            if option.count():
                try:
                    option.click(timeout=1000)
                    return
                except Exception:
                    pass

    def select_filter_value(self, label_text: str, option_text: str) -> None:
        selector = self.page.locator(
            f"xpath=//*[contains(normalize-space(), '{label_text}')]/following::*[contains(@class, 'ant-select-selector')][1]"
        ).first
        if selector.count():
            selector.click()
            self.page.get_by_text(option_text, exact=True).first.click()
            self.page.wait_for_timeout(500)

    def close_visible_modal_or_drawer(self) -> None:
        close_buttons = self.page.locator(".ant-modal-close, .ant-drawer-close")
        for index in range(close_buttons.count()):
            button = close_buttons.nth(index)
            try:
                if button.is_visible(timeout=500):
                    button.click()
                    self.page.wait_for_timeout(800)
                    return
            except Exception:
                continue

    def close_all_visible_modals_or_drawers(self) -> None:
        for _ in range(5):
            close_buttons = self.page.locator(".ant-modal-close, .ant-drawer-close")
            clicked = False
            for index in range(close_buttons.count() - 1, -1, -1):
                button = close_buttons.nth(index)
                try:
                    if button.is_visible(timeout=300):
                        button.click()
                        self.page.wait_for_timeout(500)
                        clicked = True
                        break
                except Exception:
                    continue
            if not clicked:
                return

    def click_visible_modal_button(self, button_texts: Iterable[str], timeout: int = 3000) -> None:
        last_error: Exception | None = None
        for button_text in button_texts:
            buttons = self.page.locator(f".ant-modal button:has-text('{button_text}'), button:has-text('{button_text}')")
            for index in range(buttons.count() - 1, -1, -1):
                button = buttons.nth(index)
                try:
                    expect(button).to_be_visible(timeout=timeout)
                    button.click(force=True)
                    return
                except Exception as exc:
                    last_error = exc

        raise AssertionError(f"未找到可点击的弹窗按钮：{list(button_texts)}") from last_error

    def click_ant_modal_button_by_span_text(self, span_texts: Iterable[str], timeout: int = 3000) -> None:
        last_error: Exception | None = None
        for span_text in span_texts:
            buttons = self.page.locator(
                "xpath=//div[contains(@class,'ant-modal') and not(contains(@style,'display: none'))]"
                f"//button[.//span[normalize-space()='{span_text}']]"
            )
            for index in range(buttons.count() - 1, -1, -1):
                button = buttons.nth(index)
                try:
                    expect(button).to_be_visible(timeout=timeout)
                    try:
                        button.click(timeout=timeout)
                    except Exception:
                        button.evaluate("(element) => element.click()")
                    return
                except Exception as exc:
                    last_error = exc

        raise AssertionError(f"未找到可点击的弹窗按钮 span：{list(span_texts)}") from last_error

    def _fill_version_resource_keyword(self, resource_name: str) -> None:
        filled = self.page.evaluate(
            """([resourceName]) => {
              const modal = Array.from(document.querySelectorAll('.ant-modal'))
                .reverse()
                .find(element => element.offsetParent !== null) || document.body;
              const inputs = Array.from(modal.querySelectorAll('input'))
                .filter(input => input.offsetParent !== null && !input.disabled);
              const preferred = inputs.find(input => {
                const text = `${input.placeholder || ''} ${input.getAttribute('aria-label') || ''}`;
                return /资源|名称|编码|keyword|name|code/i.test(text);
              }) || inputs[inputs.length - 1];
              if (!preferred) return false;
              preferred.focus();
              preferred.value = '';
              preferred.dispatchEvent(new Event('input', { bubbles: true }));
              preferred.value = resourceName;
              preferred.dispatchEvent(new Event('input', { bubbles: true }));
              preferred.dispatchEvent(new Event('change', { bubbles: true }));
              return true;
            }""",
            [resource_name],
        )
        assert filled, f"Could not find resource search input: {resource_name}"

    def _click_version_resource_query(self) -> None:
        clicked = self.page.evaluate(
            """() => {
              const modal = Array.from(document.querySelectorAll('.ant-modal'))
                .reverse()
                .find(element => element.offsetParent !== null) || document.body;
              const buttons = Array.from(modal.querySelectorAll('button'))
                .filter(button => button.offsetParent !== null && !button.disabled);
              const queryButton = buttons.find(button => /查询|搜索/.test(button.innerText || ''));
              if (!queryButton) return false;
              queryButton.click();
              return true;
            }"""
        )
        assert clicked, "Could not find resource query button"

    def _version_resource_row(self, resource_name: str) -> Locator:
        tree_nodes = self.page.locator(".ant-modal:visible .ant-tree-treenode", has_text=resource_name)
        if tree_nodes.count():
            return tree_nodes.first

        rows = self.page.locator(".ant-modal:visible .ant-table-row, .ant-modal:visible tr")
        for index in range(rows.count()):
            row = rows.nth(index)
            try:
                if row.is_visible(timeout=300) and resource_name in row.inner_text(timeout=1000):
                    return row
            except Exception:
                continue
        return self.page.locator(".ant-table-row", has_text=resource_name).first

    def click_button_in_container(
        self,
        container: Locator,
        button_texts: Iterable[str],
        timeout: int = 3000,
    ) -> None:
        last_error: Exception | None = None
        for button_text in button_texts:
            buttons = container.locator(f"button:has-text('{button_text}')")
            for index in range(buttons.count() - 1, -1, -1):
                button = buttons.nth(index)
                try:
                    expect(button).to_be_visible(timeout=timeout)
                    button.click(force=True)
                    return
                except Exception as exc:
                    last_error = exc

        raise AssertionError(f"未找到可点击按钮：{list(button_texts)}") from last_error

    def find_latest_operation_log_row(self, target_code: str, operation_info: str) -> Locator:
        rows = self.page.locator(".ant-table-row")
        for index in range(rows.count()):
            row = rows.nth(index)
            try:
                row_text = row.inner_text(timeout=1000)
            except Exception:
                continue
            if target_code in row_text and operation_info in row_text and "成功" in row_text:
                expect(row).to_be_visible(timeout=3000)
                return row
        raise AssertionError(f"未找到最新成功操作日志：target_code={target_code}, operation_info={operation_info}")

    def open_operation_log_detail(self, row: Locator) -> None:
        self.click_row_action(row, ["查看"])
        self.page.wait_for_timeout(1000)

    def assert_operation_log_detail(self, target_code: str, operation_info: str) -> None:
        detail = self.page.locator(".ant-modal, .ant-drawer, body").filter(has_text=operation_info).last
        expect(detail).to_be_visible(timeout=5000)
        detail_text = detail.inner_text(timeout=3000)
        assert target_code in detail_text, f"操作日志详情目标编码不匹配，期望 {target_code}，实际：{detail_text}"
        assert operation_info in detail_text, f"操作日志详情操作信息不匹配，期望 {operation_info}，实际：{detail_text}"
        assert "成功" in detail_text, f"操作日志详情未显示成功状态，实际：{detail_text}"
        self.close_visible_modal_or_drawer()

    def find_table_row(self, text: str) -> Locator:
        rows = self.page.locator(".ant-table-row")
        for index in range(rows.count()):
            row = rows.nth(index)
            try:
                if text in row.inner_text(timeout=1000):
                    return row
            except Exception:
                continue
        return self.page.locator(".ant-table-row", has_text=text).first

    def click_row_action(self, row: Locator, action_texts: Iterable[str]) -> None:
        for action_text in action_texts:
            action = row.get_by_text(action_text, exact=True).first
            if action.count():
                action.click()
                return

        row_text = row.inner_text(timeout=1000)
        raise AssertionError(f"当前行未找到操作入口 {list(action_texts)}，行内容：{row_text}")
