"""
Shinhan Bank Scraper - Main implementation
This scraper logs into Shinhan Bank and retrieves account transaction data.
"""

import asyncio
import logging
from typing import Self, TypedDict

from playwright.async_api import Page

from .keyboards import MtkKeyboard
from .playwright_mobile import PlaywrightSessionManager

logger = logging.getLogger(__name__)


class ShinhanBankTransaction(TypedDict):
    transacted_at: str
    transaction_kind: str
    withdrawal_amount: str
    deposit_amount: str
    description: str
    balance: str
    branch: str


class ShinhanBankScraper:
    """Scraper for Shinhan Bank account information."""

    def __init__(
        self,
        headless: bool = False,
        base_url: str | None = None,
    ) -> None:
        self.base_url = base_url
        self._session_manager = PlaywrightSessionManager(headless, base_url)
        self._password_keyboard = MtkKeyboard("비밀번호")
        self._account_keyboard = MtkKeyboard("계좌비밀번호")
        self.browser = None
        self.context = None
        self.page: Page | None = None

    async def __aenter__(self) -> Self:
        """Async context manager entry."""
        self.browser, self.context, self.page = await self._session_manager.__aenter__()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """Async context manager exit."""
        await self._session_manager.__aexit__(exc_type, exc_val, exc_tb)
        self.browser = None
        self.context = None
        self.page = None

    async def check_and_close_error_popup(self) -> bool:
        """Check for error popup with 확인 button and click it."""
        try:
            # Search all frames for various popup confirm buttons
            frames = self.page.frames

            for i, frame in enumerate(frames):
                try:
                    # Check for alert layer button
                    confirm_button = await frame.query_selector("#btn_alertLayer_yes")
                    if confirm_button and await confirm_button.is_visible():
                        logger.debug("Found 확인 button (alertLayer) in frame %d, clicking...", i)
                        await confirm_button.tap()
                        logger.debug("확인 button clicked")
                        return True
                except Exception:
                    pass

                try:
                    # Check for warning popup button (in iframe with id pattern "warning_*_iframe")
                    warning_confirm = await frame.query_selector("#btn_warning_confirm")
                    if warning_confirm and await warning_confirm.is_visible():
                        logger.debug("Found 확인 button (warning) in frame %d, clicking...", i)
                        await warning_confirm.tap()
                        logger.debug("Warning 확인 button clicked")
                        return True
                except Exception:
                    pass

            return False
        except Exception:
            logger.exception("Error checking for popup")
            return False

    async def navigate_to_login(self) -> None:
        """Navigate to the Shinhan Bank login page."""
        logger.debug("Navigating to Shinhan Bank...")
        target_url = self.base_url or "https://bank.shinhan.com/rib/easy/index.jsp#210000010000"
        await self.page.goto(target_url, wait_until="networkidle", timeout=60000)
        logger.debug("Login page loaded successfully")

    async def enter_user_id(self, user_id: str) -> None:
        """Enter user ID in the login form."""
        logger.debug("Entering user ID: %s", user_id)

        # Find the user ID input field
        user_id_input = await self.page.query_selector('input[id="ibx_loginId"]')
        if not user_id_input:
            user_id_input = await self.page.query_selector('input[type="text"]')

        if not user_id_input:
            raise Exception("Could not find user ID input field")

        # Clear and enter user ID
        await user_id_input.click()
        await user_id_input.fill(user_id)
        logger.debug("User ID entered successfully")

    async def enter_password_via_keyboard(self, password: str) -> None:
        """
        Enter password using the on-screen keyboard.

        Args:
            password: The password to enter (alphanumeric and special characters)
        """
        await self._password_keyboard.enter_password(self.page, password)

    async def login(self, user_id: str, password: str) -> bool:
        """
        Perform login to Shinhan Bank.

        Args:
            user_id: The user's ID
            password: The user's password
        """
        await self.navigate_to_login()
        await self.enter_user_id(user_id)
        await self.enter_password_via_keyboard(password)

        # Click the login button
        logger.debug("Looking for login button...")

        # Wait a moment for keyboard to fully close

        # Find the specific login button in the user ID login form
        # It's the blue button right below the password field
        logger.debug("Looking for the blue login button below password field...")

        # Find the login button by ID
        login_button = await self.page.query_selector("#btn_idLogin")

        if login_button:
            logger.debug("Clicking login button...")
            try:
                self.page.once("dialog", lambda dialog: asyncio.create_task(dialog.accept()))
                await login_button.tap()
                logger.debug("Login button clicked successfully")
            except Exception as e:
                logger.warning("Tap failed, trying regular click: %s", e)
                await login_button.click()
        else:
            logger.error("Could not find login button!")

        # Wait for popup or navigation

        # Check for popup with "확인" button (limited service warning)
        logger.debug("Checking for confirmation popup...")
        try:
            # Wait for the specific confirm button (ID: btn_alertLayer_yes)
            confirm_button = await self.page.wait_for_selector("#btn_alertLayer_yes", timeout=5000, state="visible")

            if confirm_button:
                logger.debug("Found confirmation popup, clicking 확인...")
                await confirm_button.tap()
                logger.debug("Confirmation clicked successfully!")
        except Exception:
            logger.debug("No confirmation popup found or already dismissed")

        # Wait for navigation after login

        # Check for ARS authentication page
        logger.debug("Checking for ARS authentication requirement...")
        try:
            # Look for ARS authentication page elements
            # TODO: <tr id="grp_ARS" style="visibility: visible;" class="w2group" aria-hidden="false"><th id="wq_uuid_912" class="w2group w2tb_th"><div id="rdo_ARS" class="w2radio " title="ARS인증"><div class="w2radio_item w2radio_item_0"><input type="radio" class="w2radio_input" name="rdo_cert_gubn" index="0" id="rdo_ARS_input_0"><label class="w2radio_label " index="0" for="rdo_ARS_input_0">ARS인증</label></div></div></th><td id="wq_uuid_914" class="w2group w2tb_td" data-title="ARS인증"><p id="wq_uuid_915" class="w2textbox ">수신이 가능한 전화번호를 선택하신 후 [ARS인증요청] 버튼을 선택하시면, 선택한 전화번호로 전화연결 후 ARS 안내에 따라 본인여부를 확인합니다.</p><div id="wq_uuid_916" class="w2group boxTyGray03 mt10"><label id="wq_uuid_917" class="w2textbox " for="cbo_telNo">전화번호</label><div id="cbo_telNo" style="width: 165px; visibility: visible;" class="w2selectbox_native w2selectbox_native_mobile fixedWidth w2selectbox_disabled" aria-hidden="false"><div class="w2selectbox_native_innerDiv"><select class="w2selectbox_native_select" id="cbo_telNo_input_0" title="전화번호" disabled="disabled"><option>010-99**-49**</option><option>010-99**-49**</option></select></div></div><input id="tbx_telNo" style="width: 165px; display: none; visibility: hidden;" class="w2input" type="text" title="전화번호" aria-hidden="true"><a id="btn_requestArs" class="w2anchor2 btnTyGray01 medium w2anchor_disabled" href="javascript:void(null);" disabled="disabled">ARS인증요청</a></div><div id="wq_uuid_921" class="w2group listDash mt15"><ul id="wq_uuid_922" class="w2group "><li id="tbx_arsText" style="display:none" class="w2textbox ">ARS시스템 재기동 작업시간(한국시간 기준 오전 04시 ~ 오전 05시)에는 ARS 인증이 지연될 수 있습니다.</li><li id="wq_uuid_924" class="w2textbox ">로밍을 이용하시는 고객의 경우 ARS 인증절차를 위한 전화연결 시 해당 통신사의 로밍요금 정책에 의해 통신요금이 청구되오니, 이점 양지하시기 바랍니다.</li><li id="tbx_arsAbroad" style="display: block; visibility: visible;" class="w2textbox " aria-hidden="false">해외고객으로 ARS인증이나 로밍이 불가능 하신 경우 고객센터 (해외 82-2-3449-8000)로 연락하여 본인인증절차를 진행해 주시기 바랍니다. (통신요금 별도청구)</li><li id="grp_changeCust" class="w2group" aria-hidden="true" style="display: none; visibility: hidden;"><span id="wq_uuid_927" class="w2textbox ">전화번호 변경은 고객정보조회/변경화면에서 ARS인증 후 변경이 가능합니다.</span><a id="btn_searchCusInfo" class="w2anchor2 btnTyGray01 medium w2anchor_disabled" href="javascript:void(null);" disabled="disabled">고객정보조회/변경</a></li></ul></div></td></tr>
            ars_text = await self.page.query_selector("text=ARS인증")

            if ars_text:
                logger.debug("ARS authentication required!")

                # Click the ARS radio button first to enable the ARS authentication button
                logger.debug("Selecting ARS authentication method...")
                # Element: <input type="radio" class="w2radio_input" name="rdo_cert_gubn" index="0" id="rdo_ARS_input_0">
                ars_radio = await self.page.query_selector("#rdo_ARS_input_0")
                if ars_radio:
                    logger.debug("Clicking ARS radio button...")
                    await ars_radio.click()
                    logger.debug("ARS radio button selected")
                else:
                    logger.warning("Could not find ARS radio button, continuing anyway...")

                # Find and select phone number
                logger.debug("Looking for phone number selection...")
                # Select element: <select class="w2selectbox_native_select" id="cbo_telNo_input_0" title="전화번호"><option>010-99**-49**</option><option>010-99**-49**</option></select>
                phone_select = await self.page.query_selector("#cbo_telNo_input_0")

                if phone_select:
                    logger.debug("Found phone number selector, selecting first option...")
                    await phone_select.select_option(index=0)
                    logger.debug("Phone number selected")
                else:
                    logger.warning("Could not find phone selection dropdown, continuing anyway...")

                # Click the ARS authentication request button
                logger.debug("Looking for ARS authentication button...")
                # Element: <a id="btn_requestArs" class="w2anchor2 btnTyGray01 medium" href="javascript:void(null);">ARS인증요청</a>
                ars_button = await self.page.query_selector("#btn_requestArs")

                if ars_button:
                    logger.debug("Clicking ARS authentication button...")
                    await ars_button.tap()
                    logger.info("=" * 60)
                    logger.info("ARS authentication requested! Phone should ring soon...")
                    logger.info("Please answer the phone call and follow the ARS instructions.")
                    logger.info("Checking for completion every 5 seconds...")
                    logger.info("Will keep clicking 승인완료 until verification completes!")
                    logger.info("=" * 60)

                    # Element: <div id="wq_uuid_40" class="w2group btnAreaBot"><a id="btn_confirm" class="w2anchor2 btnTyBlue01 large" href="javascript:void(null);">승인완료</a></div>
                    # Poll for the "승인완료" button every 5 seconds and click it repeatedly
                    # Also check for btnConfirm which appears after ARS completes
                    max_wait_time = 120  # Wait up to 2 minutes
                    elapsed_time = 0
                    approval_completed = False

                    while elapsed_time < max_wait_time:
                        await asyncio.sleep(5)  # Wait 5 seconds between checks
                        elapsed_time += 5

                        logger.debug("Checking ARS status... (%ds elapsed)", elapsed_time)

                        # Check for warning/alert popups and close them
                        await self.check_and_close_error_popup()

                        # First, check for final confirmation button (btnConfirm) - if found, ARS is complete
                        final_confirm = None
                        for i, frame in enumerate(self.page.frames):
                            try:
                                button = await frame.query_selector("#btnConfirm")
                                if button and await button.is_visible():
                                    logger.debug("  ✓ Found final 확인 button (btnConfirm) in frame %d", i)
                                    final_confirm = button
                                    break
                            except Exception:
                                continue

                        # Also check main page
                        if not final_confirm:
                            try:
                                button = await self.page.query_selector("#btnConfirm")
                                if button and await button.is_visible():
                                    logger.debug("  ✓ Found final 확인 button (btnConfirm) on main page")
                                    final_confirm = button
                            except Exception:
                                pass

                        if final_confirm:
                            logger.debug("Checking if final 확인 button is clickable...")
                            try:
                                # Use short timeout (2s) to quickly detect if button is blocked by modal
                                await final_confirm.tap(timeout=2000)
                                logger.debug("  ✓ Final 확인 button clicked! ARS authentication complete")
                                approval_completed = True
                                await asyncio.sleep(2)
                                break
                            except Exception:
                                logger.debug("  btnConfirm blocked (modal overlay), continuing with 승인완료...")

                        # If btnConfirm not found, check for and click 승인완료 button
                        approval_button = None
                        for i, frame in enumerate(self.page.frames):
                            try:
                                button = await frame.query_selector("#btn_confirm")
                                if button and await button.is_visible():
                                    logger.debug("  ✓ Found 승인완료 button in frame %d", i)
                                    approval_button = button
                                    break
                            except Exception:
                                continue

                        if approval_button:
                            logger.debug("Clicking 승인완료 button...")
                            try:
                                await approval_button.tap()
                                logger.debug("  ✓ 승인완료 button clicked")
                            except Exception as e:
                                logger.debug("Failed to click 승인완료: %s", e)
                        else:
                            logger.debug("  Neither btnConfirm nor 승인완료 found, waiting...")

                    if not approval_completed:
                        logger.warning("ARS authentication timeout. Continuing anyway...")

                else:
                    logger.warning("Could not find ARS authentication button")
            else:
                logger.debug("No ARS authentication required")
        except Exception:
            logger.exception("Error during ARS authentication")

        # Check for any error popups after login and close them
        await self.check_and_close_error_popup()

        # Check if login was successful
        current_url = self.page.url
        logger.debug("Current URL after login: %s", current_url)

        # Check for error messages
        error_selectors = [
            'div:has-text("오류")',
            'div:has-text("실패")',
            'div:has-text("확인")',
        ]

        for selector in error_selectors:
            error_element = await self.page.query_selector(selector)
            if error_element:
                error_text = await error_element.inner_text()
                logger.debug("Possible error message: %s", error_text)

        logger.debug("Login process completed")

        return True

    async def navigate_to_transaction_history(self) -> bool:
        """
        Navigate to the transaction history page by clicking the menu.
        """
        logger.debug("Navigating to transaction history menu...")

        try:
            # Use JavaScript to click the menu item directly (it may be hidden in collapsed menu)
            logger.debug("Clicking account inquiry menu (data-code=210101000000) via JavaScript...")

            clicked = await self.page.evaluate("""() => {
                const button = document.querySelector('a[data-code="210101000000"]');
                if (button) {
                    button.click();
                    return true;
                }
                return false;
            }""")

            if clicked:
                logger.debug("✓ Clicked the account inquiry menu!")

                # Wait for page to load
                await asyncio.sleep(2)

                logger.debug("Successfully navigated to transaction history!")
                return True
            else:
                logger.error("Could not find account inquiry menu button")
                return False

        except Exception:
            logger.exception("Failed to navigate")
            return False

    async def get_transactions(
        self, account_index: int = 0, account_password: str = None
    ) -> list[ShinhanBankTransaction]:
        """
        Retrieve transaction history for an account.

        Args:
            account_index: Index of account in dropdown (0 = first, 1 = second, etc.)
            account_password: 4-digit password for the account

        Returns:
            List of transaction dictionaries
        """
        logger.debug("Retrieving transaction history for account #%d...", account_index)

        if not account_password:
            raise ValueError("account_password is required")

        # Navigate to transaction history page if not already there
        await self.navigate_to_transaction_history()

        # Step 1: Select account from dropdown
        logger.debug("Selecting account #%d from dropdown...", account_index)
        account_select = await self.page.query_selector("#sbx_accno_input_0")

        if not account_select:
            raise Exception("Could not find account select element")

        # Select by index: 0 = "선택하세요", 1 = first account, 2 = second, 3 = third
        select_index = account_index + 1
        await account_select.select_option(index=select_index)
        logger.debug("✓ Selected account #%d", account_index)

        # Step 2: Enter account password via numeric keypad
        logger.debug("Entering account password...")
        await self._account_keyboard.enter_pin(self.page, account_password)
        logger.debug("✓ Entered account password")

        # Step 3: Select 1개월 period
        logger.debug("Selecting 1개월 period...")
        radio_labels = await self.page.query_selector_all(".w2radio_label")
        if len(radio_labels) > 3:
            await radio_labels[3].click()  # Index 3 = 1개월
            logger.debug("✓ Selected 1개월 period")

        # Step 4: Click 조회 button
        logger.debug("Clicking 조회 button...")
        submit_button = await self.page.query_selector("#btn_조회")
        if not submit_button:
            raise Exception("Could not find 조회 button")

        # Some fixture pages raise alert dialogs on submit; auto-accept once
        self.page.once("dialog", lambda dialog: asyncio.create_task(dialog.accept()))

        await submit_button.click()
        logger.debug("✓ Query submitted")

        # Step 5: Extract transaction data from table
        logger.debug("Extracting transaction data...")
        transaction_table = await self.page.query_selector("#F01_grd_list_body_table")

        transactions = []

        if transaction_table:
            rows = await transaction_table.query_selector_all("tr")
            logger.debug("Found %d rows in transaction table", len(rows))

            for row in rows:
                cells = await row.query_selector_all("td")
                row_data = []

                for cell in cells:
                    text = await cell.text_content()
                    row_data.append(text.strip())

                # Skip empty rows and header rows
                if row_data and any(row_data) and len(row_data) >= 8:
                    # Parse transaction data
                    # Format: [date, time, datetime, type, withdrawal, deposit, description, balance, branch, ...]
                    transaction = {
                        "transacted_at": (row_data[2] if len(row_data) > 2 and row_data[2] else "").strip()
                        or f"{(row_data[0] if len(row_data) > 0 else '').strip()} {(row_data[1] if len(row_data) > 1 else '').strip()}".strip(),
                        "transaction_kind": row_data[3] if len(row_data) > 3 else "",
                        "withdrawal_amount": row_data[4] if len(row_data) > 4 else "0",
                        "deposit_amount": row_data[5] if len(row_data) > 5 else "0",
                        "description": row_data[6] if len(row_data) > 6 else "",
                        "balance": row_data[7] if len(row_data) > 7 else "",
                        "branch": row_data[8] if len(row_data) > 8 else "",
                    }
                    transactions.append(transaction)

            logger.debug("✓ Extracted %d transactions", len(transactions))
        else:
            logger.warning("Could not find transaction table")

        return transactions
