from __future__ import annotations

import time
from pathlib import Path

from playwright.sync_api import Page, expect

from e2e.pages.base_page import BasePage
from e2e.utils.data_factory import EnterpriseRegistrationData
from e2e.utils.bug_reporter import BugReporter
from e2e.utils.image_captcha import ImageCaptchaSolver, decode_data_image
from e2e.utils.verification_code import VerificationCodeProvider


class AccountAlreadyRegistered(Exception):
    pass


class GraphicCaptchaError(RuntimeError):
    pass


class TenantRegistrationPage(BasePage):
    def __init__(
        self,
        page: Page,
        home_url: str,
        code_provider: VerificationCodeProvider,
        image_captcha_solver: ImageCaptchaSolver,
        bug_reporter: BugReporter | None = None,
    ):
        super().__init__(page)
        self.home_url = home_url
        self.code_provider = code_provider
        self.image_captcha_solver = image_captcha_solver
        self.bug_reporter = bug_reporter

    def open_registration(self) -> None:
        self.clear_tenant_session()
        register_url = self.home_url.replace("/home", "/register")
        self.goto(register_url)
        if self.registration_form_ready(timeout=8000):
            return

        self.goto(self.home_url)
        self.maybe_click_text(["登录/注册", "登录", "注册"], timeout=3000)
        self.maybe_click_text(["现在就注册", "企业注册", "立即注册", "免费注册", "入驻", "注册"], timeout=3000)
        if self.registration_form_ready(timeout=10000):
            return

        body_text = self.page.locator("body").inner_text(timeout=5000)
        raise AssertionError(f"租户注册页未加载出注册表单，当前 URL：{self.page.url}，当前页面文本：{body_text[:1000]}")

    def registration_form_ready(self, timeout: int = 10000) -> bool:
        try:
            self.page.wait_for_selector("#register_phone", state="visible", timeout=timeout)
            return True
        except Exception:
            return False

    def login(self, phone: str, password: str) -> None:
        self.open_login_form()
        if self.current_session_ready():
            return
        phone_input = self.page.locator("#password_login_phone")
        self.fill_and_verify(phone_input, phone)

        password_input = self.page.locator("#password_login_password")
        self.fill_and_verify(password_input, password)

        self.page.get_by_text("登 录", exact=True).click()
        self.page.wait_for_load_state("domcontentloaded")
        self.page.wait_for_timeout(2000)
        self.select_default_account_if_present()

    def open_login_form(self) -> None:
        for attempt in range(4):
            if attempt == 0:
                self.clear_tenant_session()
                self.goto(self.home_url.replace("/home", "/login"))
            elif attempt == 1:
                self.goto(self.home_url)
                self.page.wait_for_load_state("domcontentloaded")
                self.page.wait_for_timeout(1500)
                self.maybe_click_text(["登录/注册", "登录"], timeout=1500)
            elif attempt == 2:
                self.goto(self.home_url.replace("/home", "/login?tab=password"))
            else:
                self.page.reload(wait_until="domcontentloaded")

            self.page.wait_for_load_state("domcontentloaded")
            if self.current_session_ready():
                return
            try:
                self.page.wait_for_selector("#password_login_phone", state="visible", timeout=8000)
                self.page.wait_for_selector("#password_login_password", state="visible", timeout=8000)
                return
            except Exception:
                self.page.wait_for_timeout(1000)

        body_text = self.page.locator("body").inner_text(timeout=5000)
        raise AssertionError(f"租户登录页未加载出账号密码登录表单，当前页面文本：{body_text[:1000]}")

    def clear_tenant_session(self) -> None:
        try:
            self.page.context.clear_cookies()
        except Exception:
            pass

    def current_session_ready(self) -> bool:
        try:
            body_text = self.page.locator("body").inner_text(timeout=3000)
        except Exception:
            return False
        return "进入后台" in body_text and "主账号" in body_text

    def select_default_account_if_present(self) -> None:
        if "/account/select" not in self.page.url and not self.text_visible("选择账号", timeout=1000):
            return

        self.page.evaluate(
            """() => {
                const candidates = Array.from(document.querySelectorAll('taro-view-core, div'))
                    .filter(el => (el.innerText || el.textContent || '').includes('主账号'));
                candidates.sort((a, b) => {
                    const ar = a.getBoundingClientRect();
                    const br = b.getBoundingClientRect();
                    return (ar.width * ar.height) - (br.width * br.height);
                });
                const target = candidates.find(el => {
                    const r = el.getBoundingClientRect();
                    return r.width > 80 && r.height > 30;
                });
                if (target) target.click();
            }"""
        )
        self.page.wait_for_load_state("domcontentloaded")
        self.page.wait_for_timeout(2000)

    def certification_passed_visible(self, enterprise_name: str) -> bool:
        self.goto(self.home_url)
        self.page.wait_for_load_state("domcontentloaded")
        self.page.wait_for_timeout(2000)
        self.select_default_account_if_present()
        body_text = self.page.locator("body").inner_text(timeout=5000)
        if enterprise_name in body_text or ("进入后台" in body_text and "主账号" in body_text):
            return True
        return self.merchant_certification_approved_visible(enterprise_name)

    def merchant_certification_approved_visible(self, enterprise_name: str) -> bool:
        urls = [
            self.home_url.replace("/home", "/merchant/certification/apply"),
            self.home_url.replace("/home", "/merchant/certification"),
            self.home_url.replace("/home", "/merchant"),
        ]
        approved_texts = [
            "审核通过",
            "认证通过",
            "已认证",
            "认证成功",
            "商户认证已通过",
            "认证状态：通过",
            "认证状态: 通过",
        ]

        for url in urls:
            try:
                self.goto(url)
                self.page.wait_for_load_state("domcontentloaded")
                self.page.wait_for_timeout(2000)
                self.select_default_account_if_present()
                body_text = self.page.locator("body").inner_text(timeout=5000)
            except Exception:
                continue

            if enterprise_name in body_text and any(text in body_text for text in approved_texts):
                return True
            if "商户认证" in body_text and any(text in body_text for text in approved_texts):
                return True
            if "提交认证" not in body_text and "选择商户类型" not in body_text and any(text in body_text for text in approved_texts):
                return True

        return False

    def register_enterprise(self, data: EnterpriseRegistrationData) -> None:
        self.open_registration()
        self.fill_account_fields(data)
        self.request_sms_code_with_graphic_captcha()

        if self.code_provider.mode == "manual":
            self.wait_for_manual_sms_code_in_page(data.phone)
        else:
            sms_code = self.code_provider.get_code(data.phone)
            sms_input = self.find_sms_code_input()
            sms_input.fill(sms_code)
            expect(sms_input).to_have_value(sms_code, timeout=3000)

        self.fill_password_fields(data.password)
        self.check_agreement()
        self.submit_registration()

        if self.text_visible("已注册", timeout=1500) or self.text_visible("账号已存在", timeout=1500):
            raise AccountAlreadyRegistered(data.phone)

        self.complete_individual_business_certification(data)

    def complete_individual_business_certification(self, data: EnterpriseRegistrationData) -> None:
        if self.bug_reporter:
            self.bug_reporter.clear()

        self.open_merchant_certification()

        if self.text_visible("选择商户类型", timeout=3000):
            self.page.get_by_text("个体工商户", exact=True).click()
            self.page.get_by_role("button", name="下一步").click()

        business_name = self.page.locator("#businessName")
        expect(business_name).to_be_visible(timeout=10000)
        business_name.fill(data.enterprise_name)

        self.page.locator(".ant-select-selector").first.click()
        self.page.get_by_text("互联网/IT", exact=True).click()

        self.page.get_by_text("个人经营", exact=True).first.click()
        self.page.locator("#phone").fill(data.phone)
        self.page.locator("#address").fill("湖北省武汉市洪山区光谷软件园E2E测试地址")
        self.page.locator("input[type='file']").first.set_input_files(str(self.certification_attachment_path()))
        self.page.wait_for_timeout(1500)
        self.record_page_or_api_errors(data.phone, "租户端商户认证-上传认证资质")

        remark = self.page.locator("#remark")
        if remark.count():
            remark.fill("E2E自动化测试认证资料")

        self.page.get_by_role("button", name="提交认证").click()
        self.page.wait_for_timeout(2000)
        self.record_page_or_api_errors(data.phone, "租户端商户认证-提交认证")

    def assert_no_page_or_api_errors(self, account: str, location: str) -> None:
        if self.bug_reporter:
            self.bug_reporter.assert_no_errors(account=account, location=location)

    def record_page_or_api_errors(self, account: str, location: str) -> None:
        if self.bug_reporter:
            self.bug_reporter.record_if_errors(account=account, location=location)

    def open_merchant_certification(self) -> None:
        if "/merchant/certification/apply" not in self.page.url:
            self.goto(self.home_url.replace("/home", "/merchant/certification/apply"))
        expect(self.page.get_by_text("商户认证").first).to_be_visible(timeout=10000)

    def certification_attachment_path(self) -> Path:
        attachment = Path("runtime/certification-placeholder.png")
        attachment.parent.mkdir(parents=True, exist_ok=True)
        if not attachment.exists():
            attachment.write_bytes(
                bytes.fromhex(
                    "89504e470d0a1a0a0000000d4948445200000001000000010802000000907753"
                    "de0000000c4944415408d763f8ffff3f0005fe02fea73581e80000000049454e44ae426082"
                )
            )
        return attachment.resolve()

    def fill_account_fields(self, data: EnterpriseRegistrationData) -> None:
        phone_input = self.page.locator("#register_phone")
        self.fill_and_verify(phone_input, data.phone)

        if self.text_visible("已注册", timeout=800) or self.text_visible("账号已存在", timeout=800):
            raise AccountAlreadyRegistered(data.phone)

        nickname_input = self.page.locator("#register_nickname")
        expect(nickname_input).to_be_visible(timeout=10000)
        nickname_input.fill(data.contact_name)

    def request_sms_code_with_graphic_captcha(self, max_attempts: int = 8) -> None:
        last_code = ""
        for attempt in range(1, max_attempts + 1):
            last_code = self.fill_graphic_captcha_if_present()
            self.click_get_code()

            if self.wait_for_sms_code_request_result(timeout_ms=6000):
                return

            if self.has_graphic_captcha_error():
                self.refresh_graphic_captcha()
                continue

            self.refresh_graphic_captcha()

        raise GraphicCaptchaError(f"图形验证码识别失败，已重试 {max_attempts} 次，最后一次识别结果：{last_code}")

    def find_sms_code_input(self):
        return self.find_first_visible(
            [
                "input[placeholder*='短信验证码']",
                "input[placeholder*='验证码']",
                "input[name*='code']",
            ],
            timeout=5000,
        )

    def wait_for_manual_sms_code_in_page(self, phone: str, timeout_seconds: int = 180) -> str:
        sms_input = self.find_sms_code_input()
        print("")
        print("[E2E manual SMS] 获取验证码已点击。")
        print(f"[E2E manual SMS] 请将手机号 {phone} 在飞书测试通知群收到的短信验证码直接填写到页面短信验证码输入框。")
        print("[E2E manual SMS] 检测到输入框有 4-8 位数字后会自动继续注册。")

        deadline = time.time() + timeout_seconds
        last_value = ""
        while time.time() < deadline:
            if self.wait_for_graphic_captcha_error(timeout_ms=100):
                self.refresh_graphic_captcha()
                self.request_sms_code_with_graphic_captcha()
                sms_input = self.find_sms_code_input()

            last_value = sms_input.input_value(timeout=1000).strip()
            if last_value.isdigit() and 4 <= len(last_value) <= 8:
                expect(sms_input).to_have_value(last_value, timeout=3000)
                return last_value
            self.page.wait_for_timeout(500)

        raise AssertionError(f"等待手动填写短信验证码超时，手机号：{phone}，当前输入框内容：{last_value}")

    def wait_for_graphic_captcha_error(self, timeout_ms: int = 2000) -> bool:
        try:
            self.page.wait_for_function(
                """() => {
                    const text = document.body ? document.body.innerText : '';
                    return [
                        '图形验证码错误',
                        '图形验证码失败',
                        '图形验证码不正确',
                        '验证码错误',
                        '验证码失败',
                        '验证码不正确',
                        '请填写正确的图形验证码'
                    ].some(item => text.includes(item));
                }""",
                timeout=timeout_ms,
            )
            return True
        except Exception:
            return False

    def wait_for_sms_code_request_result(self, timeout_ms: int = 6000) -> bool:
        deadline = time.time() + timeout_ms / 1000
        while time.time() < deadline:
            if self.has_graphic_captcha_error():
                return False
            if self.sms_code_request_succeeded():
                return True
            self.page.wait_for_timeout(300)
        return False

    def sms_code_request_succeeded(self) -> bool:
        return bool(
            self.page.evaluate(
                """() => {
                    const text = document.body ? document.body.innerText : '';
                    const successHints = [
                        '验证码已发送',
                        '发送成功',
                        '短信已发送',
                        '获取成功',
                        '请查收',
                    ];
                    if (successHints.some(item => text.includes(item))) return true;

                    const nodes = Array.from(document.querySelectorAll('button, taro-button-core, taro-view-core, div, span'));
                    const codeButtons = nodes
                      .map(el => ({
                        el,
                        text: (el.innerText || el.textContent || '').trim(),
                        disabled: Boolean(el.disabled) || el.getAttribute('aria-disabled') === 'true',
                        className: String(el.className || ''),
                        r: el.getBoundingClientRect(),
                      }))
                      .filter(item => item.r.width > 0 && item.r.height > 0)
                      .filter(item => item.text.includes('获取验证码') || /\\d+\\s*s/.test(item.text) || item.text.includes('重新获取'));
                    if (codeButtons.some(item =>
                        item.disabled ||
                        item.className.includes('disabled') ||
                        /\\d+\\s*s/.test(item.text) ||
                        item.text.includes('重新获取')
                    )) {
                        return true;
                    }

                    return false;
                }"""
            )
        )

    def fill_graphic_captcha_if_present(self) -> str:
        captcha_input = self.page.locator("input[placeholder*='图形验证码']").first
        if captcha_input.count() == 0:
            return ""
        expect(captcha_input).to_be_visible(timeout=5000)

        image_src = self._find_graphic_captcha_image_src()
        captcha_code = self.image_captcha_solver.solve(decode_data_image(image_src))
        captcha_input.fill(captcha_code)
        expect(captcha_input).to_have_value(captcha_code, timeout=3000)
        return captcha_code

    def _find_graphic_captcha_image_src(self) -> str:
        src = self.page.evaluate(
            """() => {
                const input = Array.from(document.querySelectorAll('input'))
                    .find(el => (el.getAttribute('placeholder') || '').includes('图形验证码'));
                if (!input) return '';
                const inputBox = input.getBoundingClientRect();
                const images = Array.from(document.querySelectorAll('img'))
                    .map(img => ({ img, box: img.getBoundingClientRect(), src: img.getAttribute('src') || '' }))
                    .filter(item => item.src.startsWith('data:image'))
                    .filter(item => item.box.width > 40 && item.box.height > 20)
                    .filter(item => Math.abs(item.box.y - inputBox.y) < 30 && item.box.x > inputBox.x);
                images.sort((a, b) => a.box.x - b.box.x);
                return images.length ? images[0].src : '';
            }"""
        )
        if not src:
            raise AssertionError("未找到图形验证码图片")
        return src

    def refresh_graphic_captcha(self) -> None:
        image = self.page.locator("img[src^='data:image']").filter(has_not_text="").nth(0)
        # The captcha image is the data image closest to the graphic captcha input, so use JS for a precise click.
        self.page.evaluate(
            """() => {
                const input = Array.from(document.querySelectorAll('input'))
                    .find(el => (el.getAttribute('placeholder') || '').includes('图形验证码'));
                if (!input) return;
                const inputBox = input.getBoundingClientRect();
                const images = Array.from(document.querySelectorAll('img'))
                    .map(img => ({ img, box: img.getBoundingClientRect(), src: img.getAttribute('src') || '' }))
                    .filter(item => item.src.startsWith('data:image'))
                    .filter(item => item.box.width > 40 && item.box.height > 20)
                    .filter(item => Math.abs(item.box.y - inputBox.y) < 30 && item.box.x > inputBox.x);
                images.sort((a, b) => a.box.x - b.box.x);
                if (images.length) images[0].img.click();
            }"""
        )
        self.page.wait_for_timeout(500)

    def has_graphic_captcha_error(self) -> bool:
        error_texts = [
            "图形验证码错误",
            "图形验证码失败",
            "图形验证码不正确",
            "验证码错误",
            "验证码失败",
            "验证码不正确",
            "请填写正确的图形验证码",
        ]
        return any(self.text_visible(text, timeout=500) for text in error_texts)

    def click_get_code(self) -> None:
        try:
            self.click_exact_text("获取验证码")
            return
        except AssertionError:
            pass

        self.click_first_visible(
            [
                "taro-button-core:has-text('获取验证码')",
                "taro-view-core:has-text('获取验证码')",
                "button:has-text('获取验证码')",
                ".ant-btn:has-text('获取验证码')",
            ],
            timeout=3000,
        )

    def fill_password_fields(self, password: str) -> None:
        password_input = self.page.locator("#register_password")
        expect(password_input).to_be_visible(timeout=10000)
        password_input.fill(password)

        confirm_password = self.page.locator("#register_confirmPassword")
        expect(confirm_password).to_be_visible(timeout=10000)
        confirm_password.fill(password)

    def check_agreement(self) -> None:
        agreement = self.page.locator("input[type='checkbox'], .ant-checkbox-input").first
        if agreement.count():
            try:
                if not agreement.is_checked(timeout=1000):
                    agreement.check(timeout=1000, force=True)
                return
            except Exception:
                pass
        self.maybe_click_text(["我已阅读并同意", "用户服务协议"], timeout=1000)

    def submit_registration(self) -> None:
        for text in ("注 册", "注册"):
            try:
                self.click_exact_text(text)
                return
            except AssertionError:
                pass

        self.click_first_visible(
            [
                "taro-button-core:has-text('注 册')",
                "taro-view-core:has-text('注 册')",
                "taro-button-core:has-text('注册')",
                "taro-view-core:has-text('注册')",
                "button:has-text('注册')",
                ".ant-btn:has-text('注册')",
            ]
        )

    def fill_enterprise_profile_if_present(self, data: EnterpriseRegistrationData) -> None:
        enterprise_inputs = self.page.locator(
            "input[placeholder*='企业名称'], input[placeholder*='公司名称'], input[name*='enterprise'], input[name*='company']"
        )
        if enterprise_inputs.count() == 0:
            return

        try:
            self.fill_first_visible(
                [
                    "input[placeholder*='企业名称']",
                    "input[placeholder*='公司名称']",
                    "input[name*='enterprise']",
                    "input[name*='company']",
                ],
                data.enterprise_name,
                timeout=1500,
            )
        except AssertionError:
            return

        contact_input = self.page.locator("input[placeholder*='联系人'], input[name*='contact']").first
        if contact_input.count():
            contact_input.fill(data.contact_name)

        self.maybe_click_text(["提交认证", "提交审核", "提交", "保存"], timeout=2000)

    def logout_if_needed(self) -> None:
        if self.maybe_click_text(["退出登录", "退出", "注销"], timeout=1000):
            return
        avatar = self.page.locator(".avatar, [class*='avatar'], [class*='user']").first
        if avatar.count():
            try:
                avatar.click(timeout=1000)
                self.maybe_click_text(["退出登录", "退出", "注销"], timeout=1000)
            except Exception:
                pass
