from __future__ import annotations

import logging
import os
from datetime import datetime
import time

from athenaidx.workflows.base_workflow import BaseWorkflow
from athenaidx.config import Config
from athenaidx.pages.login_page import LoginPage
from athenaidx.pages.modals.payment_codes import PaymentCodesModal
from athenaidx.pages.modals.reset_modal import ResetModal
from athenaidx.toolbars.VTB import VTB
from athenaidx.toolbars.user_menu import UserMenu
from athenaidx.toolbars.HOGScreen import HOGScreen
from athenaidx.pages.payment_posting.pp_bulk import PP_Bulk
from athenaidx.pages.payment_posting.pp_main import PICScreen_Main
from athenaidx.pages.payment_posting.pp_lipp import PP_LIPP
from athenaidx.pages.payment_posting.pp_lipp_rejections import PP_LIPP_Rejections
from athenaidx.pages.payment_posting.pp_batch import PaymentPostingBatch
from athenaidx.pages.payment_posting.pp_select_patient import PP_SelectPatient
from athenaidx.model_objects.rejections import Rejections, RejectionGrouped

logger = logging.getLogger(__name__)

class PostLineItemRejections(BaseWorkflow):
    """Workflow adapted from the legacy `existing.py` script.

    Contract (inputs/outputs):
    - ctx.config: Config instance with an active Selenium driver
    - ctx.data: RejectionGrouped | list[RejectionGrouped] | list[Rejections]
    - On success: posts rejections in the application. Returns None.
    - On error: raises and logs; caller may inspect ctx.meta for details.
    """

    name = "file_post_rejections"

    def _build_steps(self) -> None:
        self._steps = [
            self.setup,
            self.login_step,
            self.process_groups,
            self.finalize,
        ]

    # ---------- steps ----------
    def setup(self) -> None:
        # create a per-run log folder (keeps parity with the old script)
        today = datetime.now()
        time_str = today.strftime("%Y-%m-%d %H %M")
        log_folder_path = os.path.join("logs", today.strftime("%Y"), today.strftime("%Y %m"), today.strftime("%Y %m %d"), time_str)
        os.makedirs(log_folder_path, exist_ok=True)

        debug_path = os.path.join(log_folder_path, f"debug_{time_str}.log")
        info_path = os.path.join(log_folder_path, f"info_{time_str}.log")

        # Basic logging configuration (file + console)
        logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
        fh = logging.FileHandler(debug_path)
        fh.setLevel(logging.DEBUG)
        self.ctx.meta["log_folder"] = log_folder_path

        logger.addHandler(fh) if hasattr(logger, "addHandler") else None

        # Ensure config exists
        if not isinstance(self.ctx.config, Config):
            raise TypeError("ctx.config must be a athenaidx.config.Config instance")

    def login_step(self) -> None:
        config = self.ctx.config
        login = LoginPage(config)
        # Credentials are expected to be managed via environment or the config
        username = os.getenv("IDX_USERNAME") or input("Enter IDX username: ")
        password = os.getenv("IDX_PASSWORD") or input("Enter IDX password: ")
        login.login(username, password)

    def _normalize_to_groups(self) -> list[RejectionGrouped]:
        data = self.ctx.data
        # If single RejectionGrouped
        if isinstance(data, RejectionGrouped):
            return [data]

        # If list of RejectionGrouped
        if isinstance(data, list) and data and all(isinstance(x, RejectionGrouped) for x in data):
            return data

        # If list of Rejections
        if isinstance(data, list) and data and all(isinstance(x, Rejections) for x in data):
            return RejectionGrouped.group_by_group_num(data)

        # Empty or unsupported
        return []

    def process_groups(self) -> None:
        config = self.ctx.config
        vtb = VTB(config)
        pp_batch = PaymentPostingBatch(config)
        pic_screen = PICScreen_Main(config)

        groups = self._normalize_to_groups()
        if not groups:
            logger.info("No groups to process")
            return

        for group_obj in groups:
            group = group_obj.group_id
            group_data = group_obj.rejections

            if not group_data:
                logger.info(f"No data for group {group}, skipping.")
                continue

            # change group if needed
            try:
                current = pic_screen.get_current_batch_group()
            except Exception:
                current = None

            if current != group:
                user_menu = UserMenu(config)
                user_menu.open_HOG_screen()
                hog_screen = HOGScreen(config)
                if group is not None:
                    hog_screen.change_group(int(group))

            vtb.open_payment_posting()

            pp_batch.open_batch()
            batch_number = pp_batch.batch_number
            logger.info(f"Processing group {group} with batch number: {batch_number}")
            time.sleep(2)

            for rejection in group_data:
                posted = False
                try:
                    # normalize batch number to int when possible (Rejections.BatchNumber is Optional[int])
                    try:
                        rejection.BatchNumber = int(batch_number) if batch_number and str(batch_number).isdigit() else None
                    except Exception:
                        rejection.BatchNumber = None
                    logger.info(f"Processing patient: {rejection.InvoiceNumber} in batch: {batch_number}")

                    select_patient = PP_SelectPatient(config)
                    select_patient.reset_patient()
                    patient_changed = select_patient.select_patient(str(rejection.InvoiceNumber))

                    if patient_changed is not True and patient_changed:
                        logger.warning(f"Modal detected during patient selection: {patient_changed}")
                        if isinstance(rejection, Rejections):
                            rejection.Comments = f"Modal during selection: {patient_changed}"
                        if 'group' in str(patient_changed).lower():
                            continue

                    if not getattr(rejection, 'Paycode', None):
                        pic_screen.open_paycode_modal()
                        pc_modal = PaymentCodesModal(config)
                        paycode = pc_modal.get_paycode_options()
                        if paycode == "":
                            logger.warning(f"No valid paycode found for patient {rejection.InvoiceNumber}, skipping.")
                            if isinstance(rejection, Rejections):
                                rejection.Comments = "No valid paycode found"
                            continue
                        rejection.Paycode = paycode

                    pc_entered = pic_screen.enter_paycode(rejection.Paycode or "")
                    if not pc_entered:
                        logger.warning(f"Failed to enter paycode for patient {rejection.InvoiceNumber}, skipping.")
                        if isinstance(rejection, Rejections):
                            rejection.Comments = "Failed to enter paycode"
                        continue

                    pic_screen.set_line_item_post_checkbox(rejection.LineItemPost)

                    reset_modal = ResetModal(config)
                    modal_text = reset_modal.close_if_present()
                    if modal_text is not None:
                        logger.info(f"Modal detected during rejection entry: {modal_text}")
                        if modal_text == 'Line Item Payments Only':
                            pc_entered = pic_screen.enter_paycode(rejection.Paycode or "")
                            if not pc_entered:
                                logger.warning(f"Failed to enter paycode for patient {rejection.InvoiceNumber}, skipping.")
                                if isinstance(rejection, Rejections):
                                    rejection.Comments = "Failed to enter paycode"
                                continue
                            pic_screen.set_line_item_post_checkbox(rejection.LineItemPost)

                    if rejection.LineItemPost:
                        pp_lipp = PP_LIPP(config)
                        starting_index, num_cpts_to_post = pp_lipp.num_rows_to_process()

                        pp_lipp.populate_row(starting_index, rejection)

                        pp_lipp_rej = PP_LIPP_Rejections(config, rejection)
                        pp_lipp_rej.enter_carrier(str(rejection.Carrier or ""))
                        reset_modal = ResetModal(config)
                        _ = reset_modal.close_if_present()
                        pp_lipp_rej.close_screen()

                        if num_cpts_to_post > 1 and starting_index > 1:
                            num_cpts_to_post = num_cpts_to_post + 1

                        for cpt_row in range(starting_index + 1, num_cpts_to_post + 1):
                            logger.debug(f"Processing CPT row {cpt_row} of {num_cpts_to_post}")
                            pp_lipp.populate_row(cpt_row, rejection)
                        posted = pp_lipp.finalize_posting()
                    else:
                        pp_bulk = PP_Bulk(config)
                        if pp_bulk.enter_bulk_pp_screen():
                            posted = pp_bulk.enter_rejection_remarks(rejection)

                    if posted:
                        rejection.Completed = True
                    else:
                        logger.error(f"Failed to post for patient {rejection.InvoiceNumber}")
                        rejection.Comments = "Failed to post, did not post rejection to all lines"
                        pp_batch.open_batch()

                except Exception as e:
                    logger.exception(f"Unexpected error processing patient {getattr(rejection, 'InvoiceNumber', '<unknown>')}: {e}")
                    try:
                        pp_batch.open_batch()
                    except Exception:
                        logger.exception("Failed to recover after error; aborting group processing")
                        break

    def finalize(self) -> None:
        # sign out and quit driver if available
        try:
            user_menu = UserMenu(self.ctx.config)
            user_menu.sign_out()
        except Exception:
            logger.debug("Sign out failed or not available")

        try:
            driver = self.ctx.config.get_driver()
            driver.quit()
        except Exception:
            logger.debug("Driver quit failed or driver not present")

