"""
Web test runner - executes natural language test steps using AI agent in browser.
"""

import asyncio
import json
import os
import re
import sys
import uuid
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any
from dotenv import load_dotenv
from playwright.async_api import async_playwright

from backend.core.logger import get_logger, log_info, log_error, log_warning, log_step
from agents.web_agent import AIAdhocWebAgent

# Ensure backend utils is on path for remaining imports
_backend_dir = Path(__file__).resolve().parent.parent
if str(_backend_dir) not in sys.path:
    sys.path.insert(0, str(_backend_dir))

from integrations.azure_blob import BlobStorageManager
from reporting.reporter import ConsolidatedHTMLReporter
from integrations.ado_integration import fetch_and_parse_test_cases
from services.skip_service import is_test_case_skipped
from services.tag_service import (
    resolve_requested_vendor_tag,
    filter_test_cases_by_vendor_tag,
    format_vendor_tag,
    filter_test_case_tags,
)


class WebRunner:
    """
    Test runner that executes natural language test steps using AI agent.
    Manages browser automation, test execution, and report generation.
    """

    def __init__(self, test_steps_file: str = "test_steps_web.json"):
        load_dotenv()
        # Resolve paths relative to backend directory
        self.base_dir = Path(__file__).resolve().parent.parent
        self._utils_dir = self.base_dir / "utils"
        self.reports_dir = self.base_dir / "reporting" / "reports" / "consolidated reports"
        self.screenshots_dir = self.base_dir / "reporting" / "reports" / "screenshots"
        self.test_steps_file = self._utils_dir / test_steps_file

        self.logger = get_logger(__name__)

        # Azure DevOps config
        self.ado_plan_id = os.getenv("ADO_PLAN_ID")
        self.ado_suite_id = os.getenv("ADO_SUITE_ID")
        self.ado_testcase_ids = os.getenv("ADO_TESTCASE_ID", "").strip() or None

        self.reports_dir.mkdir(parents=True, exist_ok=True)
        self.screenshots_dir.mkdir(exist_ok=True)

        self.login_url = os.getenv("LOGIN_URL", "https://teams.microsoft.com/")
        self.login_email = os.getenv("LOGIN_EMAIL", "")
        self.login_password = os.getenv("LOGIN_PASSWORD", "")
        self.total_episodes = int(os.getenv("TOTAL_EPISODES", "1"))
        self.include_hcltech_testcase_id = (
            str(os.getenv("HCLTech_TestCaseId", "true")).strip().lower() == "true"
        )

        self.blob_manager = None
        self.test_run_id = str(uuid.uuid4())
        try:
            if os.getenv("AZURE_STORAGE_CONNECTION_STRING"):
                self.blob_manager = BlobStorageManager()
                log_info("[OK]Blob storage enabled.")
        except Exception as e:
            log_warning(f"[ISSUE]Blob storage disabled: {e}")

        self.test_start_time = datetime.now()
        self.steps_log: List[Dict[str, Any]] = []
        self.current_episode = 1
        self.testcase_results: List[Dict[str, Any]] = []

    def load_test_steps(self) -> List[Dict[str, Any]]:
        """Load test cases either from Azure DevOps (preferred) or fallback JSON."""
        try:
            if self.ado_plan_id and self.ado_suite_id:
                log_info(
                    f"Loading test cases from ADO Plan {self.ado_plan_id}, Suite {self.ado_suite_id}"
                )
                test_cases = fetch_and_parse_test_cases(
                    self.ado_plan_id,
                    self.ado_suite_id,
                    testcase_ids=self.ado_testcase_ids,
                )
                return test_cases
            else:
                log_info("No ADO plan/suite configured. Falling back to JSON file.")
                with open(self.test_steps_file, "r", encoding="utf-8") as f:
                    test_cases = json.load(f)
                if isinstance(test_cases, list):
                    return [filter_test_case_tags(tc) for tc in test_cases]
                return test_cases
        except Exception as e:
            log_error(f"[ERROR]Error loading test steps: {e}")
            return []

    async def take_screenshot(self, page, step_number: int) -> str:
        """Take screenshot and optionally upload to blob storage."""
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"step_{int(step_number):03d}_{timestamp}.png"
        screenshot_path = self.screenshots_dir / filename

        try:
            await page.screenshot(path=str(screenshot_path))

            if self.blob_manager:
                try:
                    blob_url = self.blob_manager.upload_screenshot(
                        str(screenshot_path), self.test_run_id
                    )
                    return blob_url
                except Exception as e:
                    log_warning(f"[ISSUE]Screenshot upload failed: {e}")
                    return str(screenshot_path)
            else:
                return str(screenshot_path)

        except Exception as e:
            log_error(f"[ERROR]Error taking screenshot: {e}")
            return ""

    def _log_step(
        self,
        step_number: int,
        step_description: str,
        result: str,
        screenshot: str,
        action_details: Dict[str, Any] = None,
    ):
        """Log test step execution details."""
        step_log = {
            "step": step_number,
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "description": step_description,
            "result": result,
            "screenshot": screenshot,
            "action_details": action_details or {},
            "episode": self.current_episode,
        }
        self.steps_log.append(step_log)
        log_step(step_number, step_description, result, self.logger)

    async def execute_test_case(self, page, test_case: Dict[str, Any]) -> bool:
        """Execute a single test case with natural language steps."""
        title = test_case.get("title", "Untitled Test")
        tc_id = test_case.get("id")
        title_display = f"[{tc_id}] {title}" if tc_id else title
        steps = test_case.get("steps", [])
        expected_result = test_case.get("expected_result", "")

        log_info(f"\n{'='*100}")
        log_info(f"Executing Test Case: {title_display}")
        log_info(f"Expected Result: {expected_result}")
        log_info(f"Total Steps: {len(steps)}")
        log_info(f"\n{'='*100}")
        log_info(f"Starting Test Case: {title_display}")
        case_start_time = datetime.now()
        log_info(f"Start Time: {case_start_time.strftime('%Y-%m-%d %H:%M:%S')}")

        success = True
        case_start_index = len(self.steps_log)

        for i, step in enumerate(steps, 1):
            step_number = i
            try:
                screenshot_before = await self.take_screenshot(page, step_number)

                try:
                    if any(
                        keyword in step.lower()
                        for keyword in ["login", "switch", "sign"]
                    ):
                        await page.wait_for_load_state("networkidle", timeout=5000)
                except Exception:
                    pass

                result = await self.ai_agent.run_natural_test_step(page, step)

                try:
                    if re.search(
                        r"\bopen\b.*family\s+safety\s+web(page)?",
                        step,
                        re.IGNORECASE,
                    ):
                        try:
                            await page.wait_for_load_state("load", timeout=2000)
                        except Exception:
                            pass
                        await asyncio.sleep(5)
                except Exception:
                    pass
                screenshot_after = await self.take_screenshot(
                    page, int(step_number + 0.5)
                )

                if result["success"]:
                    self._log_step(
                        step_number=step_number,
                        step_description=step,
                        result="passed",
                        screenshot=screenshot_after,
                        action_details={
                            "action": result["action"],
                            "confidence": result["action"].get("confidence", 0),
                            "reasoning": result["action"].get("reasoning", ""),
                            "element_info": result["details"].get("element_info", {}),
                            "frame": result["details"].get("frame", "main"),
                        },
                    )
                    log_info(f"✓ Step {i} completed successfully")

                    if any(
                        keyword in step.lower()
                        for keyword in ["login", "switch", "sign"]
                    ):
                        try:
                            await page.wait_for_load_state(
                                "networkidle", timeout=5000
                            )
                        except Exception:
                            pass
                        await asyncio.sleep(2)
                else:
                    self._log_step(
                        step_number=step_number,
                        step_description=step,
                        result="failed",
                        screenshot=screenshot_before,
                        action_details={
                            "error": result["error"],
                            "action": result["action"],
                        },
                    )
                    log_error(f"✗ Step {i} failed: {result['error']}")
                    success = False
                    log_warning(
                        f"[STOP]Halting remaining steps for '{title_display}' "
                        f"after failure at step {step_number}"
                    )
                    break

                await asyncio.sleep(1)

            except Exception as e:
                screenshot = await self.take_screenshot(page, step_number)
                self._log_step(
                    step_number=step_number,
                    step_description=step,
                    result="error",
                    screenshot=screenshot,
                    action_details={"exception": str(e)},
                )
                log_error(f"✗ Step {i}/{len(steps)} error: {e}")
                success = False
                log_warning(
                    f"[STOP]Halting remaining steps for '{title_display}' "
                    f"after error at step {step_number}"
                )
                break

        case_steps = self.steps_log[case_start_index:]
        case_end_time = datetime.now()
        case_duration = case_end_time - case_start_time
        case_duration_str = str(case_duration).split(".")[0]
        case_passed = (
            all(s.get("result") == "passed" for s in case_steps)
            if case_steps
            else success
        )

        microsoft_ado_id = int(test_case.get("id", 0))
        hcltech_ado_id = 0
        if "[" in title and "]" in title:
            match = re.search(r"\[(\d+)\]", title)
            if match:
                hcltech_ado_id = int(match.group(1))

        failure_message = ""
        for step in case_steps:
            if step.get("result") in ["failed", "error"]:
                failure_message = step.get("action_details", {}).get("error", "")
                break

        last_screenshot = ""
        for entry in reversed(case_steps):
            if entry.get("screenshot"):
                last_screenshot = entry["screenshot"]
                break

        try:
            cleaned_test_case_title = re.sub(
                r"^\s*\[[^\]]+\]\s*", "", title
            ).strip()
        except Exception:
            cleaned_test_case_title = title

        priority_value = ""
        try:
            m = re.search(r"\[(P[0-4])\]", title, re.IGNORECASE)
            if m:
                priority_value = m.group(1).upper()
            else:
                m = re.search(
                    r"\[\s*Priority\s*[:\-]?\s*(High|Medium|Low)\s*\]",
                    title,
                    re.IGNORECASE,
                )
                if m:
                    priority_value = m.group(1).title()
                else:
                    m = re.search(r"\b(P[0-4])\b", title, re.IGNORECASE)
                    if m:
                        priority_value = m.group(1).upper()
        except Exception:
            priority_value = ""

        if not priority_value:
            priority_value = str(
                test_case.get("priority", test_case.get("Priority", ""))
            )

        self.testcase_results.append(
            {
                "id": tc_id,
                "title": title,
                "Test Case ID": hcltech_ado_id,
                **(
                    {"HCLTech ADO ID": microsoft_ado_id}
                    if self.include_hcltech_testcase_id
                    else {}
                ),
                "Test Case Title": cleaned_test_case_title,
                "Priority": priority_value,
                "Status": "Passed" if case_passed else "Failed",
                "Start Time": case_start_time.strftime("%Y-%m-%d %H:%M:%S"),
                "End Time": case_end_time.strftime("%Y-%m-%d %H:%M:%S"),
                "Duration": case_duration_str,
                "Failure Message": failure_message,
                "steps": case_steps,
                "last_screenshot": last_screenshot,
            }
        )

        return success

    def generate_reports(self):
        """Generate JSON and HTML reports."""
        try:
            columns = ["Test Case ID"]
            if self.include_hcltech_testcase_id:
                columns.append("HCLTech ADO ID")
            columns.extend(
                [
                    "Priority",
                    "Test Case Title",
                    "Duration",
                    "Status",
                    "Failure Message",
                    "Start Time",
                    "End Time",
                ]
            )
            report_payload = {
                "columns": columns,
                "rows": self.testcase_results,
                "test_start_time": self.test_start_time.strftime(
                    "%Y-%m-%d %H:%M:%S"
                ),
                "test_run_id": self.test_run_id,
                "total_test_cases": len(self.testcase_results),
            }
            latest_json_path = self.reports_dir / "latest_report.json"
            with latest_json_path.open("w", encoding="utf-8") as f:
                json.dump(report_payload, f, ensure_ascii=False, indent=2)
            log_info(f"[OK]Wrote latest_report.json with {len(self.steps_log)} steps")

            if self.testcase_results:
                all_testcase_results = [
                    {
                        "title": tc.get("title", "Untitled Test"),
                        "status": (
                            tc.get("Status") or tc.get("status", "failed")
                        ).lower(),
                        "steps": tc.get("steps", []),
                        "last_screenshot": tc.get("last_screenshot", ""),
                        "id": tc.get("Test Case ID"),
                    }
                    for tc in self.testcase_results
                ]
            else:
                all_testcase_results = [
                    {
                        "title": "Natural Language Run",
                        "status": (
                            "passed"
                            if report_payload.get("failed_steps", 0) == 0
                            else "failed"
                        ),
                        "steps": self.steps_log,
                        "last_screenshot": next(
                            (
                                e.get("screenshot", "")
                                for e in reversed(self.steps_log)
                                if e.get("screenshot")
                            ),
                            "",
                        ),
                    }
                ]

            self.all_testcase_results = all_testcase_results

            timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
            html_path = self.reports_dir / f"report_{timestamp}.html"
            ConsolidatedHTMLReporter(
                all_testcase_results,
                html_report=str(html_path),
                test_start_time=self.test_start_time,
                test_end_time=datetime.now(),
            ).generate_html_report()
            log_info(f"[OK]HTML Report generated: {html_path.name}")
            return html_path

        except Exception as e:
            log_error(f"[ERROR]Error generating reports: {e}")

    def generate_summary(self):
        """Generate steps summary and return report data."""
        report_payload = {
            "steps": self.steps_log,
            "test_start_time": self.test_start_time.strftime(
                "%Y-%m-%d %H:%M:%S"
            ),
            "test_run_id": self.test_run_id,
            "total_steps": len(self.steps_log),
            "passed_steps": sum(
                1 for step in self.steps_log if step["result"] == "passed"
            ),
            "failed_steps": sum(
                1
                for step in self.steps_log
                if step["result"] in ["failed", "error"]
            ),
        }
        steps_only_path = self.reports_dir / "latest_steps_summary.json"
        with steps_only_path.open("w", encoding="utf-8") as f:
            json.dump(report_payload, f, ensure_ascii=False, indent=2)
        log_info(
            f"[OK]Wrote latest_steps_summary.json with {len(self.steps_log)} steps"
        )

        if self.testcase_results:
            all_testcase_results = [
                {
                    "title": tc.get("title", "Untitled Test"),
                    "status": (
                        tc.get("Status") or tc.get("status", "failed")
                    ).lower(),
                    "steps": tc.get("steps", []),
                    "last_screenshot": tc.get("last_screenshot", ""),
                    "id": tc.get("id"),
                }
                for tc in self.testcase_results
            ]
        else:
            all_testcase_results = [
                {
                    "title": "Natural Language Run",
                    "status": (
                        "passed"
                        if report_payload.get("failed_steps", 0) == 0
                        else "failed"
                    ),
                    "steps": self.steps_log,
                    "last_screenshot": next(
                        (
                            e.get("screenshot", "")
                            for e in reversed(self.steps_log)
                            if e.get("screenshot")
                        ),
                        "",
                    ),
                }
            ]

        timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
        html_path = self.reports_dir / f"report_{timestamp}.html"
        data_for_steps = ConsolidatedHTMLReporter(
            all_testcase_results,
            html_report=str(html_path),
            test_start_time=self.test_start_time,
            test_end_time=datetime.now(),
        ).generate_html_report()

        return data_for_steps

    async def run(self):
        """
        Main entry point: run all test cases.
        Returns path to HTML report or None.
        """
        return await self.run_tests()

    async def run_tests(self):
        """Execute all test cases and generate reports."""
        log_info(
            f"[OK]Test started at: {self.test_start_time.strftime('%Y-%m-%d %H:%M:%S')}"
        )
        log_info(f"[OK]Total episodes configured: {self.total_episodes}")

        test_cases = self.load_test_steps()
        if not test_cases:
            log_error("[ERROR]No test cases found. Exiting.")
            return None
        log_info(f"[OK]Loaded {len(test_cases)} test cases")

        requested_vendor_tag = resolve_requested_vendor_tag(argv=sys.argv)
        if requested_vendor_tag:
            log_info(
                f"[Tags] Vendor tag filter requested: "
                f"{format_vendor_tag(requested_vendor_tag)}"
            )
        else:
            log_info(
                "[Tags] No vendor tag filter specified. "
                "Running all test cases (excluding #skip)."
            )

        filtered_cases, excluded_cases = filter_test_cases_by_vendor_tag(
            test_cases, requested_vendor_tag
        )

        if requested_vendor_tag:
            log_info(
                f"[Tags] {len(filtered_cases)} test cases match "
                f"{format_vendor_tag(requested_vendor_tag)}; "
                f"{len(excluded_cases)} excluded (including #skip)."
            )
            if not filtered_cases:
                log_error("[Tags] No test cases matched the requested tag. Exiting.")
                return None
        else:
            log_info(
                f"[Tags] {len(filtered_cases)} test cases selected; "
                f"{len(excluded_cases)} excluded (#skip test cases)."
            )

        test_cases = filtered_cases
        log_info(f"[Tags] Proceeding with {len(test_cases)} filtered test cases")

        html_report = None

        async with async_playwright() as p:
            browser = await p.chromium.launch(headless=False, channel="msedge")
            context = await browser.new_context()
            page = await context.new_page()

            try:
                await page.goto(self.login_url, wait_until="load")
                log_info(f"[OK]Navigated to {self.login_url}")

                self.ai_agent = AIAdhocWebAgent(
                    email=self.login_email,
                    password=self.login_password,
                )
                if self.login_email and self.login_password:
                    try:
                        await self.ai_agent.login(page)
                    except Exception as e:
                        log_warning(f"[ISSUE]Login encountered issues: {e}")

                for test_case in test_cases:
                    try:
                        if is_test_case_skipped(test_case):
                            title = test_case.get("title", "Untitled Test")
                            tc_id = test_case.get("id")
                            title_display = (
                                f"[{tc_id}] {title}" if tc_id else title
                            )
                            log_info(
                                f"Excluding Test Case (tagged): {title_display}"
                            )
                            continue
                    except Exception:
                        pass

                    success = await self.execute_test_case(page, test_case)
                    if not success:
                        log_warning(
                            f"[ISSUE]Test case "
                            f"'{test_case.get('title', 'Unknown')}' had failures"
                        )

                html_report = self.generate_reports()

            except Exception as e:
                log_error(f"[ERROR]Error during test execution: {e}")
                try:
                    error_screenshot = await self.take_screenshot(page, 999)
                    log_warning(
                        f"[ISSUE]Error screenshot saved: {error_screenshot}"
                    )
                except Exception:
                    pass
            finally:
                await context.close()
                await browser.close()

        test_end_time = datetime.now()
        test_duration = test_end_time - self.test_start_time

        log_info(f"\n{'='*60}")
        log_info("TEST EXECUTION SUMMARY")
        log_info(f"{'='*60}")
        log_info(
            f"Test started: {self.test_start_time.strftime('%Y-%m-%d %H:%M:%S')}"
        )
        log_info(
            f"Test completed: {test_end_time.strftime('%Y-%m-%d %H:%M:%S')}"
        )
        log_info(f"Total duration: {test_duration.total_seconds():.1f} seconds")
        log_info(f"Total steps: {len(self.steps_log)}")
        log_info(
            f"Passed: {sum(1 for step in self.steps_log if step['result'] == 'passed')}"
        )
        log_info(
            f"Failed: {sum(1 for step in self.steps_log if step['result'] in ['failed', 'error'])}"
        )
        log_info(f"Test run ID: {self.test_run_id}")
        return html_report

    def get_summary_data(self):
        """Return the summary data after tests have run."""
        return self.generate_summary()

    async def cleanup(self):
        """Cleanup resources and close browser instances."""
        try:
            if hasattr(self, "ai_agent") and self.ai_agent:
                if hasattr(self.ai_agent, "browser") and self.ai_agent.browser:
                    await self.ai_agent.browser.close()
                if hasattr(self.ai_agent, "context") and self.ai_agent.context:
                    await self.ai_agent.context.close()
            log_info("Cleanup completed successfully")
        except Exception as e:
            log_error(f"Error during cleanup: {e}")


# Backward compatibility alias
NaturalLanguageTestRunner = WebRunner
