
import json
import os
import bs4
import re
import sys
from datetime import datetime
from typing import Any, Dict, List, Optional
import math
import json
import sys
from collections import Counter, defaultdict


# Constants
DEFAULT_JSON_PATH = "c:\\reports\\json_files"
FAILED_TESTS_JSON = "failed_testscripts.json"
PASSED_TESTS_JSON = "passed_testscripts.json"
SUMMARY_WIDGET_KEY = "widgets/summary.json"
TEST_CASES_PATTERN = r'"data[\\/]+test-cases[\\/]+(?P<uid>[^"]+?)\.json"\s*:\s*"(?P<json>((?:[^"\\]|\\.)*))"'
TEST_ID_PATTERN = r"(T\d+)"
ENCODINGS = ["utf-8", "latin1", "cp1252", "iso-8859-1"]
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"

# Configure JSON output directory from environment variable or use default
json_file_path = os.environ.get("JSON_FILE_PATH", DEFAULT_JSON_PATH)
if json_file_path == DEFAULT_JSON_PATH:  # Check for unset environment variable
    print("JSON_FILE_PATH is not set. Defaulting to c:\\reports\\json_files")
else:
    print(f"JSON file path: {json_file_path}")

# Ensure the JSON output directory exists
if not os.path.exists(json_file_path):
    os.makedirs(json_file_path, exist_ok=True)
    print(f"Created JSON output directory: {json_file_path}")


def get_filename(directory: str) -> str:
    try:
        # Find html files in the directory
        files = os.listdir(directory)
        html_files = [file for file in files if file.endswith(".html")]
        return html_files[0] if html_files else ""
    except Exception as e:
        print(f"Error in getting filename: {e}")
        return ""

def summarize_html_report(report_path: str) -> Dict[str, Any]:
    summary, _ = complete_html_report(report_path)
    return summary

def complete_html_report(report_path: str) -> Dict[str, Any]:
    """
    Summarizes an Allure test report by extracting test execution statistics and timing information.

    This function processes an Allure HTML report file to extract comprehensive test execution
    data including timing information, test counts by status, and calculates the pass rate.
    It handles JSON data embedded within the HTML report and performs necessary data cleaning
    and validation.

    Args:
        report_path (str): Path to the Allure HTML report file

    Returns:
        Dict[str, Any]: A dictionary containing:
            - start_time: Test execution start time (YYYY-MM-DD HH:MM:SS)
            - stop_time: Test execution stop time (YYYY-MM-DD HH:MM:SS)
            - duration: Total duration in hours, minutes, and seconds
            - total: Total number of tests
            - passed: Number of passed tests
            - failed: Number of failed tests
            - broken: Number of broken tests
            - skipped: Number of skipped tests
            - unknown: Number of tests with unknown status
            - pass_rate: Percentage of passed tests (XX.XX%)

    Raises:
        FileNotFoundError: If the report file doesn't exist
        ValueError: If the report format is invalid or required data is missing
        json.JSONDecodeError: If the report contains invalid JSON data
        KeyError: If required data fields are missing from the report

    Example:
        >>> summary = summarize_html_report("report.html")
        >>> print(summary["pass_rate"])
        '90.00%'
    """
    directory = os.path.dirname(report_path)
    file_name = os.path.basename(report_path)
    print(f"\nFile name: {file_name}")
    # Initialize default summary with zero values
    summary = {
        "start_time": "",
        "stop_time": "",
        "duration": "0 hours 0 minutes 0 seconds",
        "total": 0,
        "passed": 0,
        "failed": 0,
        "broken": 0,
        "skipped": 0,
        "unknown": 0,
        "pass_rate": "0.00%",
    }

    try:
        # Read the report file
        if not os.path.exists(report_path):
            raise FileNotFoundError(f"Report file not found: {report_path}")

        with open(report_path, "r", encoding="utf-8", errors="ignore") as file:
            content = file.read()

        # Locate and extract embedded JSON summary data from HTML
        index = content.find(SUMMARY_WIDGET_KEY)
        if index == -1:
            raise ValueError("Could not find summary data in the report file")

        # Extract the JSON string from embedded HTML content
        # The JSON is embedded as: "widgets/summary.json": "{...}"});
        trim_at_start = f'{SUMMARY_WIDGET_KEY}": "'
        content = content.replace(trim_at_start, "")
        content = content[index:]

        # Find the end of the JSON object (marked by "};")
        index = content.find("};")
        if index == -1:
            raise ValueError("Invalid report format: missing closing bracket")

        # Extract JSON content and clean up formatting
        content = content[0 : index - 4]  # Remove trailing ", };
        content = content.replace("\\", "")  # Unescape backslashes for valid JSON

        # Parse the JSON content
        try:
            content = json.loads(content)
        except json.JSONDecodeError as e:
            raise ValueError(f"Invalid JSON data in report: {str(e)}")

        # Extract and validate required data
        if "statistic" not in content or "time" not in content:
            raise KeyError("Missing required data fields in report")

        content["statistic"]
        time_data = content["time"]

        # Extract and format test execution timing information
        try:
            start_time = time_data["start"]  # Epoch timestamp in milliseconds
            stop_time = time_data["stop"]  # Epoch timestamp in milliseconds
            duration_ms = time_data["duration"]  # Duration in milliseconds

            # Convert milliseconds to seconds for datetime conversion
            duration = int(duration_ms / 1000)

            # Convert epoch timestamps to human-readable date/time format
            start_time_human = datetime.fromtimestamp(start_time / 1000).strftime(
                DATE_FORMAT
            )
            stop_time_human = datetime.fromtimestamp(stop_time / 1000).strftime(
                DATE_FORMAT
            )

            # Break down total duration into hours, minutes, and seconds
            duration_hours = int(duration / 3600)
            duration_minutes = int((duration - duration_hours * 3600) / 60)
            duration_seconds = int(
                duration - duration_hours * 3600 - duration_minutes * 60
            )

            # Format and store timing information
            summary["start_time"] = start_time_human
            summary["stop_time"] = stop_time_human
            summary["duration"] = (
                f"{duration_hours} hours {duration_minutes} minutes {duration_seconds} seconds"
            )
        except (KeyError, ValueError, TypeError) as e:
            print(f"Warning: Error processing timing data: {str(e)}")
            print("Timing information will show default values")

        # Process test statistics
        try:
            test_stats = get_unique_test_count(report_path)
            summary["total"] = len(test_stats["total"])
            summary["passed"] = len(test_stats["passed"])
            summary["failed"] = len(test_stats["failed"])
            summary["broken"] = len(test_stats["broken"])
            summary["skipped"] = len(test_stats["skipped"])
            summary["unknown"] = len(test_stats["unknown"])

            print("Failed: ", test_stats["failed"])
            print("Broken: ", test_stats["broken"])
            # Calculate pass rate
            if summary["total"] > 0:
                summary["pass_rate"] = (
                    f"{summary['passed'] / summary['total'] * 100:.2f}%"
                )
            else:
                summary["pass_rate"] = "0.00%"
        except KeyError as e:
            print(f"Warning: Missing statistic field: {str(e)}")
        except ZeroDivisionError:
            print("Warning: Total test count is zero")

    except Exception as e:
        print(f"Error processing report: {str(e)}")

    return summary, test_stats


def save_summary_to_file(summary: Dict[str, Any], file_path: str):
    """
    Saves the summary data to a text file in a human-readable format.

    This function takes a summary dictionary and writes its contents to a text file,
    formatting the keys to be more readable (e.g., "start_time" becomes "Start Time").
    The output is both printed to console and written to the specified file.

    Args:
        summary (Dict[str, Any]): The summary data to save
        file_path (str): The path where the summary will be saved

    Raises:
        IOError: If the file cannot be written

    Example:
        >>> summary = summarize_html_report("report.html")
        >>> save_summary_to_file(summary, "report_summary.txt")
        Start Time: 2024-03-01 10:00:00
        Stop Time: 2024-03-01 11:00:00
        Duration: 1 hours 0 minutes 0 seconds
        ...
    """
    try:
        with open(file_path, "w", encoding="utf-8") as file:
            for key, value in summary.items():
                # Format the key for display (replace underscores with spaces and capitalize)
                display_key = key.replace("_", " ").title()
                print(f"{display_key}: {value}")
                file.write(f"{display_key}: {value}\n")
        print(f"\nSummary saved to: {file_path}")
    except IOError as e:
        print(f"Error saving summary file: {str(e)}")


def get_report_path(attachment_path: str) -> str:
    """
    Gets the path to the latest report file in the specified directory.

    This function combines the directory path with the first HTML file found
    to create a complete file path for report processing.

    Args:
        attachment_path (str): The path to the directory containing report files

    Returns:
        str: The complete path to the report file

    Raises:
        FileNotFoundError: If the report directory doesn't exist
        ValueError: If no report files are found in the directory

    Example:
        >>> report_path = get_report_path("reports/")
        'reports/report_20240301.html'
    """
    try:
        # Get the latest report filename
        filename = get_filename(attachment_path)
        if not filename:
            print("No report file found")
            exit(1)
        # Generate the full report path
        report_path = os.path.join(attachment_path, filename)
        return report_path
    except Exception as e:
        print(f"Error in getting report path: {str(e)}")


def get_summary_message(summary: Dict[str, Any]) -> str:
    """
    Converts a summary dictionary into a formatted string message.

    This function transforms the summary dictionary into a human-readable string
    format, with each key-value pair on a new line. Keys are formatted to be
    more readable (e.g., "start_time" becomes "Start Time").

    Args:
        summary (Dict[str, Any]): The summary data to convert

    Returns:
        str: A formatted string message containing the summary data

    Example:
        >>> summary = summarize_html_report("report.html")
        >>> message = get_summary_message(summary)
        >>> print(message)
        Start Time: 2024-03-01 10:00:00
        Stop Time: 2024-03-01 11:00:00
        Duration: 1 hours 0 minutes 0 seconds
        Total tests: 100
        Passed: 90
        Failed: 10
        Broken: 0
        Skipped: 0
        Unknown: 0
        Pass rate: 90.00%
        ...
    """
    summary_message = ""
    try:
        for k, v in summary.items():
            # Format the key for display (replace underscores with spaces and capitalize)
            key = k.replace("_", " ").title()
            summary_message += f"{key}: {v}\n"
    except Exception as e:
        print(f"Error in getting summary message: {str(e)}")
    return summary_message


def get_file_size_in_mb(file_path: str) -> str:
    """
    Calculate and format the size of a file in megabytes.

    This function determines the size of a specified file in megabytes,
    formats the result with 2 decimal places, and appends the 'MB' suffix.

    Args:
        file_path (str): Absolute or relative path to the file

    Returns:
        str: The file size formatted as a string with 'MB' suffix (e.g., "1.23 MB").
             Returns "0 MB" if the file cannot be accessed or an error occurs.

    Raises:
        None: All exceptions are caught internally and return "0 MB"

    Example:
        >>> size = get_file_size_in_mb("allure-report/index.html")
        >>> print(size)
        '2.45 MB'

        >>> size = get_file_size_in_mb("nonexistent.html")
        >>> print(size)
        '0 MB'
    """
    try:
        file_size = os.path.getsize(file_path)
        return f"{file_size / (1024 * 1024):.2f} MB"
    except Exception as e:
        print(f"Error in getting file size: {str(e)}")
        return "0 MB"


def read_html_file(html_file: str) -> Optional[str]:
    encodings = ENCODINGS

    for encoding in encodings:
        try:
            with open(html_file, "r", encoding=encoding) as file:
                return file.read()
        except UnicodeDecodeError:
            continue
        except Exception as e:
            print(f"Error reading file with {encoding} encoding: {e}")
            continue

    print("Failed to read file with any of the attempted encodings")
    return None


def extract_test_data(html_content: str) -> Optional[Dict]:
    """
    Extracts test data from the HTML content by parsing embedded JSON.

    This function locates and extracts the test data JSON from the HTML content,
    handling escaped characters and JSON parsing. It specifically looks for the
    'data/packages.json' section in the HTML file.

    Args:
        html_content (str): The HTML content to parse

    Returns:
        Optional[Dict]: The parsed test data as a dictionary, or None if extraction fails

    Raises:
        ValueError: If the JSON data cannot be found or is invalid

    Example:
        >>> content = read_html_file("report.html")
        >>> test_data = extract_test_data(content)
        >>> if test_data:
        ...     print(f"Found {len(test_data['children'])} test items")
    """
    try:
        key = '"data/packages.json":'
        start_index = html_content.find(key)
        if start_index == -1:
            raise ValueError("'data/packages.json' not found in source.")

        json_start = start_index + len(key)
        substring = html_content[json_start:].lstrip()

        if not substring.startswith('"'):
            raise ValueError("JSON string not properly formatted.")

        end_quote_index = None
        escape = False
        for i in range(1, len(substring)):
            if substring[i] == '"' and not escape:
                end_quote_index = i
                break
            elif substring[i] == "\\" and not escape:
                escape = True
            else:
                escape = False

        if end_quote_index is None:
            raise ValueError("End of JSON string not found.")

        json_escaped = substring[1:end_quote_index]
        json_unescaped = json.loads(f'"{json_escaped}"')
        return json.loads(json_unescaped)
    except Exception as e:
        print(f"Error in extract_test_data: {e}")
        return None

def get_sub_json_step_status(steps: dict, collected=None) -> str:
    """
    Get the status of the sub JSON step.
    """
    for step in steps:
        if collected is None:
            collected = []
        collected.append(step.get("status"))

        # handle nested steps
        #if step.get("steps"):
        #    get_sub_json_step_status(step["steps"], collected)

def extract_test_cases_by_uid(html_content: str, html_file_name: str) -> Dict[str, dict]:
    """
    Extract test case data from Allure HTML reports using unique identifiers.

    This function parses Allure HTML content to extract individual test case data
    embedded as JSON strings. Each test case is identified by a unique UID (Universal
    Identifier) and contains detailed information about test execution, status, and metadata.

    The function uses regex pattern matching to locate embedded JSON data for test cases,
    handling escaped characters and unicode encoding properly.

    Args:
        html_content (str): The complete HTML content of an Allure report as a string.
                           This should contain embedded JSON data for individual test cases.

    Returns:
        Dict[str, dict]: A dictionary mapping test case UIDs to their parsed data.
                        Each value is a dictionary containing test case information such as:
                        - name: Test case name
                        - status: Test execution status
                        - testStage: Test execution stages and steps
                        - parameters: Test parameters (if any)
                        - And other Allure-specific metadata

    Raises:
        None: Exceptions during JSON parsing are caught and logged, but don't interrupt
              processing of other test cases.

    Example:
        >>> html_content = read_html_file("allure-report/index.html")
        >>> test_cases = extract_test_cases_by_uid(html_content, "report1.html")
        >>> print(f"Found {len(test_cases)} test cases")
        Found 150 test cases

        >>> first_uid = list(test_cases.keys())[0]
        >>> print(test_cases[first_uid]['name'])
        'Test user login functionality'
    """
    pattern = TEST_CASES_PATTERN
    matches = re.finditer(pattern, html_content)

    uid_to_testcase: Dict[str, dict] = {}
    test_cases_list: List[Dict[str, Any]] = []
    testcases_json_file = os.path.join(json_file_path, "test_execution.json")

    if os.path.exists(testcases_json_file):
        with open(testcases_json_file, "r", encoding="utf-8") as f:
            test_cases_list = json.load(f)

    for i, match in enumerate(matches):
        uid = match.group("uid")
        escaped_json = match.group("json")

        try:
            unescaped = bytes(escaped_json, "utf-8").decode("unicode_escape")
            data = json.loads(unescaped)
            id = data.get("name").split(":")[0].strip()
            name = data.get("name").split(":")[1].strip().replace(",", ".")
            if "--" in name:
                name = name.split("--")[0].strip()
            else:
                name = name.strip()
            steps = data.get("testStage", {}).get("steps", [])
            collected = []
            # Test status is not always correct, so we need to check the sub JSON step status.
            get_sub_json_step_status(steps, collected)
            if "failed" in collected:
                status = "Fail"
            elif "skipped" in collected:
                status = "Skip"
            elif "passed" in collected:
                status = "Pass"
            else:
                status = "Unknown"

            # If test staus is broken due to script issues, then set the status to broken.
            if "broken" in data.get("status").lower():
                status = "Broken"

            duration = format_time(data.get("time").get("duration"))
            time_seconds = data.get("time").get("duration") / 1000
            if time_seconds <= 60:
                runtime = 1
            else:
                time_minutes = time_seconds / 60
                runtime = math.ceil(time_minutes)

            start_time = data.get("time").get("start")
            end_time = data.get("time").get("stop")
            start_time_human = datetime.fromtimestamp(start_time / 1000).strftime(
                "%Y-%m-%d %H:%M:%S"
            )
            end_time_human = datetime.fromtimestamp(end_time / 1000).strftime(
                "%Y-%m-%d %H:%M:%S"
            )
            test_cases_list.append(
                {
                    "id": id,
                    "name": name,
                    "status": status,
                    "starttime": start_time_human,
                    "endtime": end_time_human,
                    "runtime": runtime,
                    "duration": duration,
                    "filename": os.path.basename(html_file_name),  # Use the basename of the html file name
                }
            )
            uid_to_testcase[uid] = data
        except Exception as e:
            print(f"[{i}] Error parsing test case for UID {uid}: {e}")

        with open(testcases_json_file, "w", encoding="utf-8") as f:
            json.dump(test_cases_list, f, indent=2)
 
    return uid_to_testcase


def format_time(time: int) -> str:
    """
    Format time in milliseconds to a human-readable string.

    Args:
        time (int): Time duration in milliseconds

    Returns:
        str: Formatted string in format "Xh Ym Zs"
    """
    hours = time // 3600000
    minutes = (time % 3600000) // 60000
    seconds = (time % 60000) // 1000
    return f"{hours}h {minutes}m {seconds}s"

def get_test_ids_by_status(test_items: List[Dict], status: str) -> List[str]:
    """
    Extract and return sorted test case IDs for a given status.

    This function filters test items by status and extracts their test case IDs
    (e.g., "T123") from their names. The IDs are returned in sorted order.

    Args:
        test_items (List[Dict]): List of test item dictionaries
        status (str): The status to filter by (e.g., "passed", "failed")

    Returns:
        List[str]: Sorted list of test case IDs for the given status

    Example:
        >>> test_items = [{"name": "Test T123", "status": "passed"}]
        >>> ids = get_test_ids_by_status(test_items, "passed")
        >>> print(ids)
        ['T123']
    """
    test_ids = []

    for item in test_items:
        test_name = item.get("name", "")
        top_status = item.get("status", "").lower()
        steps = item.get("testStage", {}).get("steps", [])
        step_failed = any(step.get("status", "").lower() == "failed" for step in steps)

        # Determine corrected status (failed takes precedence if any step failed)
        corrected_status = "failed" if step_failed else top_status

        if corrected_status == status.lower():
            match = re.search(TEST_ID_PATTERN, test_name)
            test_ids.append(match.group(1) if match else test_name)

    return sorted(test_ids)


def get_unique_test_count(html_file_path: str) -> Dict[str, List[str]]:
    """
    Analyze test results and return unique test case IDs categorized by status.

    This function performs comprehensive analysis of Allure HTML reports to extract
    unique test case identifiers grouped by their execution status. It handles complex
    status determination including step-level failure detection and ensures accurate
    categorization of test results.

    The function also automatically exports failed and passed test IDs to JSON files
    for integration with other tools and further analysis.

    Args:
        html_file_path (str): Absolute or relative path to the Allure HTML report file

    Returns:
        Dict[str, List[str]]: Dictionary containing sorted lists of unique test case IDs:
            - "total": List of all unique test case IDs found in the report
            - "passed": List of test case IDs that passed execution
            - "failed": List of test case IDs that failed (including step-level failures)
            - "broken": List of test case IDs with broken status
            - "skipped": List of test case IDs that were skipped
            - "unknown": List of test case IDs with unknown status

    Raises:
        None: File reading errors return a failure message string instead of raising exceptions

    Side Effects:
        - Creates/updates "failed_testscripts.json" with sorted failed test IDs
        - Creates/updates "passed_testscripts.json" with sorted passed test IDs
        - Files are saved to the directory specified by JSON_FILE_PATH environment variable

    Example:
        >>> stats = get_unique_test_count("allure-report/index.html")
        >>> print(f"Total unique tests: {len(stats['total'])}")
        Total unique tests: 100

        >>> print(f"Failed tests: {stats['failed'][:3]}")
        Failed tests: ['T001', 'T015', 'T042']

        # JSON files are automatically created:
        # failed_testscripts.json and passed_testscripts.json
    """
    try:
        test_stats: Dict[str, List[str]] = {}

        # Read and preprocess the HTML content
        html_content = read_html_file(html_file_path)
        if not html_content:
            print("Failed to read HTML file - check file path and encoding")
            return {
                "total": [],
                "passed": [],
                "failed": [],
                "broken": [],
                "skipped": [],
                "unknown": [],
            }

        # Clean HTML content to handle escaped quotes in file paths
        html_content = html_content.replace('File "', "File ").replace('py "', "py ")

        # Extract individual test case data from embedded JSON
        test_data = extract_test_cases_by_uid(html_content, html_file_path)
        test_items = list(test_data.values())
        print(f"Total items: {len(test_items)}")

        # Extract all unique test case IDs from test items
        all_tests = []
        for test in test_items:
            name = test.get("name", "")
            # Extract test ID using regex pattern T followed by digits
            match = re.search(TEST_ID_PATTERN, name)
            test_id = match.group(1) if match else name
            all_tests.append(test_id)
        # Remove duplicates while preserving order
        all_tests = list(dict.fromkeys(all_tests))

        # Categorize test case IDs by their execution status
        passed_ids = get_test_ids_by_status(test_items, "passed")
        failed_ids = get_test_ids_by_status(test_items, "failed")
        broken_ids = get_test_ids_by_status(test_items, "broken")
        skipped_ids = get_test_ids_by_status(test_items, "skipped")
        unknown_ids = get_test_ids_by_status(test_items, "unknown")

        passed_ids = list(dict.fromkeys(passed_ids))
        failed_ids = list(dict.fromkeys(failed_ids))
        broken_ids = list(dict.fromkeys(broken_ids))
        skipped_ids = list(dict.fromkeys(skipped_ids))
        unknown_ids = list(dict.fromkeys(unknown_ids))

        # Create combined list of all non-passing test IDs for filtering
        non_passing_ids = set(failed_ids + broken_ids + skipped_ids + unknown_ids)

        # Ensure passed tests don't appear in failed/broken categories (status precedence)
        passed_ids = [
            test_id for test_id in passed_ids if test_id not in non_passing_ids
        ]
        passed_ids = list(set(passed_ids))

        # Remove failed tests from broken category (failed takes precedence over broken)
        broken_ids = [test_id for test_id in broken_ids if test_id not in failed_ids]
        broken_ids = list(set(broken_ids))

        failed_ids.sort()
        passed_ids.sort()
        broken_ids.sort()
        skipped_ids.sort()
        unknown_ids.sort()

        # Compile final test statistics with categorized test IDs
        test_stats["total"] = all_tests
        test_stats["passed"] = passed_ids
        test_stats["failed"] = failed_ids
        test_stats["broken"] = broken_ids
        test_stats["skipped"] = skipped_ids
        test_stats["unknown"] = unknown_ids

    except Exception as e:
        print(f"Error in analysing test results: {e}")

    return test_stats


def write_failed_test_ids_to_file(failed_ids: List[str]) -> None:
    try:
        # Ensure output directory exists
        os.makedirs(json_file_path, exist_ok=True)

        # Save failed test IDs to JSON file with proper formatting
        output_file = os.path.join(json_file_path, FAILED_TESTS_JSON)
        with open(output_file, "w", encoding="utf-8") as file:
            json.dump(sorted(failed_ids), file, indent=4, ensure_ascii=False)
        print(f"\nFailed test IDs saved to: {output_file}")
        print(f"Failed test IDs: {sorted(failed_ids)}")
    except Exception as e:
        print(f"Error saving failed test IDs to file: {e}")


def write_passed_test_ids_to_file(passed_ids: List[str]) -> None:
    """
    Export passed test case IDs to a JSON file for external analysis.

    This function saves a sorted list of passed test case IDs to a JSON file,
    enabling tracking of successful test executions and integration with other
    tools for success analysis or reporting purposes.

    Args:
        passed_ids (List[str]): List of test case IDs that passed execution.
                              IDs should follow the format "T###" (e.g., "T002", "T056").

    Returns:
        None: This function does not return a value.

    Raises:
        None: All exceptions are caught internally and logged to console.

    Side Effects:
        - Creates or overwrites "passed_testscripts.json" in the JSON_FILE_PATH directory
        - Creates the output directory if it doesn't exist
        - Prints error messages to console if file operations fail

    Example:
        >>> passed_tests = ["T002", "T003", "T056", "T078", "T099"]
        >>> write_passed_test_ids_to_file(passed_tests)
        # Creates: json_files/passed_testscripts.json
        # Content: ["T002", "T003", "T056", "T078", "T099"]
    """
    try:
        # Ensure output directory exists
        os.makedirs(json_file_path, exist_ok=True)

        # Save passed test IDs to JSON file with proper formatting
        output_file = os.path.join(json_file_path, PASSED_TESTS_JSON)
        with open(output_file, "w", encoding="utf-8") as file:
            json.dump(sorted(passed_ids), file, indent=4, ensure_ascii=False)
        print(f"\nPassed test IDs saved to: {output_file}")
        print(f"Passed test IDs: {sorted(passed_ids)}")
    except Exception as e:
        print(f"Error saving passed test IDs to file: {e}")


def _print_version_and_exit() -> None:
    """Print package version and exit. Used for --version flag."""
    try:
        from importlib.metadata import version
        print(f"allure-report-analyzer {version('allure-report-analyzer')}")
    except Exception:
        try:
            from allure_report_summarizer import __version__
            print(f"allure-report-analyzer {__version__}")
        except Exception:
            print("allure-report-analyzer (unknown version)")
    sys.exit(0)


def summarize_allure_report(save_failures: bool = False, save_passed: bool = False):
    """
    Command-line interface for Allure Report Summarizer.

    This block executes when the script is run directly (not imported as a module).
    It processes environment variables, locates the Allure report, and generates
    a summary of test execution results.
    """
    if "--version" in sys.argv or "-V" in sys.argv:
        _print_version_and_exit()

    # Retrieve Allure report directory from environment variable
    allure_report_path = os.environ.get("ALLURE_REPORT_PATH")
    if not allure_report_path:
        print(
            "ALLURE_REPORT_PATH environment variable is not set. Example: c:\\reports"
        )
        exit(1)
    else:
        print(f"ALLURE_REPORT_PATH: {allure_report_path}")

    # Locate the HTML report file in the specified directory
    report_path = get_report_path(allure_report_path)

    # Process the report and generate summary
    if os.path.exists(report_path):
        with open(report_path, "r", encoding="utf-8") as f:
            html_content = f.read()
            html_title = get_html_title(html_content)
            if "Allure Report" not in html_title and "allure" not in html_content.lower():
                print(f"Not an Allure report: {report_path}")
                exit(1)

        summary, stats = complete_html_report(report_path)
        print("\n=== TEST EXECUTION SUMMARY ===")
        print(summary)
        
        failed_ids = stats.get("failed", [])
        passed_ids = stats.get("passed", [])
        broken_ids = stats.get("broken", [])
        if save_failures:
            write_failed_test_ids_to_file(failed_ids)
        if save_passed:
            write_passed_test_ids_to_file(passed_ids)
        status = {}
        for id in failed_ids:
            status[id] = "Fail"
        for id in passed_ids:
            status[id] = "Pass"
        for id in broken_ids:
            status[id] = "Broken"
        
        # Ensure output directory exists
        # os.makedirs(json_file_path, exist_ok=True)
        # status_file = os.path.join(json_file_path, "test_status.json")
        #with open(status_file, "w", encoding="utf-8") as file:
        #    json.dump(status, file, indent=2, ensure_ascii=False)
        #    print(f"\nTest status saved to: {status_file}")
        print("\nExecution information processing completed")
    else:
        print(f"Allure report file not found: {report_path}")
        print(
            f"Please ensure the ALLURE_REPORT_PATH contains valid Allure HTML report files."
        )
        exit(1)


def get_html_title(html_content: str) -> str:
    """
    Get the title of the HTML file.
    """
    try:
        title = "No title found"
        soup = bs4.BeautifulSoup(html_content, "html.parser")
        title = soup.find("title").string
    except Exception as e:
        print(f"Error in getting HTML title: {str(e)}")
    return title


def sum_runtime_by_id_and_filename(
    input_file: str = r"test_execution.json", output_file: str = r"processed_data.json"
) -> None:
    try:
        print("\nAggregating runtime by ID and filename...")
        input_file = os.path.join(json_file_path, input_file)
        output_file = os.path.join(json_file_path, output_file)
        if not os.path.exists(input_file):
            print(f"Input file not found: {input_file}")
            return

        with open(input_file, "r", encoding="utf-8") as f:
            rows = json.load(f)

        aggregated_data = {}

        for row in rows:
            if not isinstance(row, dict):
                continue

            id_value = row.get("id")
            name_value = row.get("name")
            runtime_value = row.get("runtime")
            starttime_value = row.get("starttime")
            endtime_value = row.get("endtime")
            filename_value = row.get("filename")
            status_value = row.get("status")

            if id_value is None or filename_value is None or runtime_value is None:
                continue

            try:
                runtime = int(runtime_value) if isinstance(runtime_value, int) else 0
            except (ValueError, TypeError):
                runtime = 0

            key = (str(id_value).strip(), str(filename_value).strip())

            if key in aggregated_data:
                aggregated_data[key]["runtime"] += runtime
                aggregated_data[key]["iteration"] += 1
            else:
                aggregated_data[key] = {
                    "name": str(name_value).strip() if name_value is not None else "",
                    "runtime": runtime,
                    "starttime": (
                        str(starttime_value).strip()
                        if starttime_value is not None
                        else ""
                    ),
                    "endtime": (
                        str(endtime_value).strip()
                        if endtime_value is not None
                        else ""
                    ),
                    "status": (
                        str(status_value).strip()
                        if status_value is not None
                        else ""
                    ),
                    "iteration": 1,
                }

        output_data = []
        for (id_val, filename_val), data in sorted(aggregated_data.items()):
            output_data.append(
                {
                    "id": id_val,
                    "name": data["name"],
                    "status": data["status"],
                    "starttime": data["starttime"],
                    "endtime": data["endtime"],                    
                    "runtime": round(data["runtime"], 2),
                    "iteration": data["iteration"],
                    "filename": filename_val,                    
                }
            )

        output_dir = os.path.dirname(output_file)
        if output_dir:
            os.makedirs(output_dir, exist_ok=True)

        with open(output_file, "w", encoding="utf-8") as f:
            json.dump(output_data, f, indent=2)

        print(f"Processed data saved to: {output_file}")
        print(f"Total unique ID-FileName combinations: {len(aggregated_data)}")

    except FileNotFoundError as e:
        print(f"Error: {e}")
        raise
    except Exception as e:
        print(f"Error processing JSON file: {e}")
        raise


def remove_duplicate_ids_keep_recent(
    input_file: str = r"processed_data.json",
    output_file: str = r"test_runtime_data.json",
) -> None:
    try:
        input_file = os.path.join(json_file_path, input_file)
        if not os.path.exists(input_file):
            print(f"Input file not found: {input_file}")
            return

        output_path = os.path.join(json_file_path, output_file)

        with open(input_file, "r", encoding="utf-8") as f:
            rows_data = json.load(f)

        if not rows_data:
            print("No data rows found in the file.")
            return

        id_to_row: Dict[str, Dict[str, Any]] = {}

        for row_data in rows_data:
            if not isinstance(row_data, dict):
                continue

            id_value = (
                str(row_data.get("id", "")).strip()
                if row_data.get("id") is not None
                else ""
            )
            starttime_value = row_data.get("starttime")

            if not id_value:
                continue

            try:
                if isinstance(starttime_value, str):
                    starttime_dt = datetime.strptime(
                        starttime_value, "%Y-%m-%d %H:%M:%S"
                    )
                elif isinstance(starttime_value, datetime):
                    starttime_dt = starttime_value
                else:
                    if id_value not in id_to_row:
                        id_to_row[id_value] = dict(row_data)
                    continue
            except (ValueError, TypeError):
                if id_value not in id_to_row:
                    id_to_row[id_value] = dict(row_data)
                continue

            if id_value in id_to_row:
                existing_starttime = id_to_row[id_value].get("starttime")
                try:
                    if isinstance(existing_starttime, str):
                        existing_dt = datetime.strptime(
                            existing_starttime, "%Y-%m-%d %H:%M:%S"
                        )
                    elif isinstance(existing_starttime, datetime):
                        existing_dt = existing_starttime
                    else:
                        continue

                    if starttime_dt > existing_dt:
                        id_to_row[id_value] = dict(row_data)
                except (ValueError, TypeError):
                    continue
            else:
                id_to_row[id_value] = dict(row_data)

        output_data = []
        for id_val in sorted(id_to_row.keys()):
            row = id_to_row[id_val]
            name_value = row.get("name")
            if name_value is not None:
                name_str = str(name_value)
                if "--" in name_str:
                    row = {**row, "name": name_str.split("--")[0].strip()}
                else:
                    row = {**row, "name": name_str.strip()}

            total_runtime = row.get("runtime") or 0
            iteration = row.get("iteration") or 1
            runtime = math.ceil(total_runtime / iteration) if iteration > 0 else 0

            output_row = {**row, "avgruntime": runtime}
            output_data.append(output_row)

        output_dir = os.path.dirname(output_path)
        if output_dir:
            os.makedirs(output_dir, exist_ok=True)

        with open(output_path, "w", encoding="utf-8") as f:
            json.dump(output_data, f, indent=2)

        removed_count = len(rows_data) - len(id_to_row)
        print(
            f"\nRemoved {removed_count} duplicate ID entries. Kept most recent entries."
        )
        # print(f"Final data saved to: {output_path}")
        print(f"Total unique IDs: {len(id_to_row)}")
        # Sum of iteration values
        total_iteration = sum(row.get("iteration") for row in output_data)
        print(f"Total iterations: {total_iteration}")

    except FileNotFoundError as e:
        print(f"Error: {e}")
        
    except Exception as e:
        print(f"Error processing JSON file: {e}")
    finally:
        sort_test_runtime_data()
        cleanup_temp_files()


def sort_test_runtime_data(input_file: str = r"test_runtime_data.json") -> None:
    """
    Sort the test_runtime_data.json file by the RunTime column value.
    """
    try:
        input_file = os.path.join(json_file_path, input_file)
        if not os.path.exists(input_file):
            print(f"Input file not found: {input_file}")
            return

        filename = os.path.basename(input_file)
        # output_file = os.path.join(json_file_path, "sorted_" + filename.replace(".json", "") + ".json")
        column_name = "runtime"

        with open(input_file, "r", encoding="utf-8") as f:
            data = json.load(f)

        if not data:
            print("No data to sort.")
            return

        rows_sorted = sorted(
            data,
            key=lambda x: x.get(column_name, 0) if isinstance(x, dict) else 0,
            reverse=True,
        )

        with open(input_file, "w", encoding="utf-8") as f:
            json.dump(rows_sorted, f, indent=2)

        print(f"Sorted data saved to: {input_file}")

    except Exception as e:
        print(f"Error: {e}")


def delete_input_file(input_file: str) -> None:
    """
    Delete the input_file.
    """
    # print(f"\nDeleting {input_file}...")
    if os.path.exists(input_file):
        os.remove(input_file)
    else:
        print(f"{input_file} file not found to delete")


def cleanup_temp_files() -> None:
    delete_input_file(os.path.join(json_file_path, "processed_data.json"))
    delete_input_file(os.path.join(json_file_path, "test_execution.json"))


def get_all_files(report_path):
    html_files = []
    for root, dirs, files in os.walk(report_path):
        for file in files:
            if file.endswith(".html"):
                # Check the html title and if it contains "Allure Report" then process it
                with open(os.path.join(root, file), "r", encoding="utf-8") as f:
                    html_content = f.read()
                    html_content = html_content.replace("<!DOCTYPE html>", " ")
                    html_title = get_html_title(html_content)
                    if "Allure Report" in html_title:
                        print("Found Allure report file: ",file)
                        html_files.append(os.path.join(root, file))
    return html_files


def load_json(file):
    """Load JSON file and return data rows with column name to index mapping."""
    with open(file, "r", encoding="utf-8") as f:
        data = json.load(f)

    if not data:
        return [], {}

    # Data is list of dicts; col maps key name to key name (for compatibility)
    header = list(data[0].keys()) if data else []
    col = {h: i for i, h in enumerate(header)}

    # Convert dict rows to list format for compatibility with existing code
    rows = []
    for row in data:
        if isinstance(row, dict):
            rows.append([row.get(k) for k in header])
        else:
            rows.append(row)

    return rows, col


def line():
    print("-" * 70)


def run_summary(data, col):
    status = Counter()

    total_runtime = 0
    total_scenarios = 0

    max_runtime = None
    max_runtime_row = None

    max_scenarios = None
    max_scenario_row = None

    for r in data:

        runtime = r[col["runtime"]]
        scenarios = r[col["iteration"]]

        status[r[col["status"]]] += 1

        total_runtime += runtime
        total_scenarios += scenarios

        if max_runtime is None or runtime > max_runtime:
            max_runtime = runtime
            max_runtime_row = r

        if max_scenarios is None or scenarios > max_scenarios:
            max_scenarios = scenarios
            max_scenario_row = r

    total = len(data)
    passed = status["Pass"]
    failed = status["Fail"]
    broken = status["Broken"]

    pass_rate = (passed / total * 100) if total else 0
    avg_runtime = total_runtime / total if total else 0

    line()
    print(" " * 20 + " RUN SUMMARY " + " " * 20)
    line()

    print(f"Total Test Cases        : {total}")
    print(f"Total BDD Scenarios     : {total_scenarios}")
    print(f"Pass                    : {passed}")
    print(f"Fail                    : {failed}")
    print(f"Broken                  : {broken}")
    print(f"Pass Rate               : {pass_rate:.2f}%")

    print()
    print(
        f"Total Runtime (minutes) : {total_runtime} minutes or {total_runtime / 60:.2f} hours"
    )
    print(f"Average Runtime         : {avg_runtime:.2f} min")

    print()
    print("*" * 20 + " LONGEST TEST " + "*" * 20)
    print(f"{max_runtime_row[col['id']]}  {max_runtime} min")
    print(max_runtime_row[col["name"]])

    print()
    print("*" * 20 + " MAX SCENARIO COUNT " + "*" * 20)
    print(f"{max_scenario_row[col['id']]}  {max_scenarios} scenarios")
    print(max_scenario_row[col["name"]])

    print()


from collections import defaultdict


def runtime_by_status(data, col):

    print("-" * 70)
    print(" " * 20 + " RUNTIME BY STATUS (minutes) " + " " * 20)
    print("-" * 70)

    stats = defaultdict(lambda: {"runtime": 0, "count": 0})

    for r in data:

        status = r[col["status"]]
        runtime = r[col["runtime"]]

        stats[status]["runtime"] += runtime
        stats[status]["count"] += 1

    for status, s in stats.items():

        avg = s["runtime"] / s["count"] if s["count"] else 0

        print(
            f"{status:6}  "
            f"Tests:{s['count']:4}  "
            f"Runtime:{s['runtime']:6} min  "
            f"Avg:{avg:.2f} min"
        )

    print()


def slowest_tests(data, col, top=10):

    line()
    print(" " * 20 + f" TOP {top} SLOWEST TESTS " + " " * 20)
    line()

    sorted_tests = sorted(data, key=lambda r: r[col["runtime"]], reverse=True)

    for r in sorted_tests[:top]:
        print(
            f"{r[col['id']]:6} "
            f"{r[col['runtime']]:>4} min "
            f"{r[col['iteration']]:>3} scenario(s) "
            f"{r[col['name']][:60]}"
        )

    print()


def scenario_heavy_tests(data, col, threshold=5):

    line()
    print(" " * 20 + f" TESTS WITH SCENARIOS > {threshold} " + " " * 20)
    line()

    found = False

    for r in data:

        scenarios = r[col["iteration"]]

        if scenarios > threshold:
            found = True
            print(
                f"{r[col['id']]:6} "
                f"{scenarios:>3} scenarios "
                f"{r[col['name']][:60]}"
            )

    if not found:
        print("None")

    print()


def stats_by_file(data, col):

    line()
    print(" " * 20 + " STATS BY EXECUTION REPORT " + " " * 20)
    line()

    stats = defaultdict(lambda: {"total": 0, "pass": 0, "fail": 0, "broken": 0, "runtime": 0})

    for r in data:

        f = r[col["filename"]]
        s = r[col["status"]]
        rt = r[col["runtime"]]

        stats[f]["total"] += 1
        stats[f]["runtime"] += rt

        if s == "Pass":
            stats[f]["pass"] += 1
        elif s == "Broken":
            stats[f]["broken"] += 1
        else:
            stats[f]["fail"] += 1

    for f, s in stats.items():

        rate = (s["pass"] / s["total"] * 100) if s["total"] else 0

        print(f[:50])
        print(
            f"   Tests:{s['total']}  "
            f"Pass:{s['pass']}  "
            f"Fail:{s['fail']}  "
            f"Broken:{s['broken']}  "
            f"Runtime:{s['runtime']} min  "
            f"PassRate:{rate:.1f}%"
        )

    print()


def load_stats(file: str):
    data, col = load_json(file)

    run_summary(data, col)
    runtime_by_status(data, col)
    slowest_tests(data, col)
    scenario_heavy_tests(data, col)
    stats_by_file(data, col)

if __name__ == "__main__":
    try:
        if "--version" in sys.argv or "-V" in sys.argv:
            _print_version_and_exit()
        if len(sys.argv) > 1:
            report_path = os.environ.get("ALLURE_REPORT_PATH")
            if os.path.exists(report_path):
                print("Allure report files will be parsed from the directory: ", report_path)
            else:
                if os.path.exists(sys.argv[1]):
                    report_path = sys.argv[1]
                else:
                    print("Report path not found: ", sys.argv[1])
                    print(
                        "ALLURE_REPORT_PATH environment variable is not set. Example: c:\\reports"
                    )
                    exit(1)
        if "-all" in sys.argv:
            print("Did you mean to use --all instead of -all?")
            exit(1)
        elif "-stat" in sys.argv or "--stat" in sys.argv:
            print("Did you mean to use --stats?")
            exit(1)
        
        save_failures = False
        save_passed = False
        parse_all_reports = False
        if "--all" in sys.argv:
            parse_all_reports = True
            print("All html files will be parsed.")
        elif "--stats" in sys.argv:
            json_file_path = ""
            for arg in sys.argv:
                if arg.endswith(".json"):
                    json_file_path = arg
                    break
            if not os.path.exists(json_file_path):
                print(f"Runtime json data file not found. Please provide the path to the json file.")
                print("Usage: python report_summarize.py --stats <json_file_path>")
                exit(1)
            load_stats(json_file_path)
            exit(0)
        if "--fail" in sys.argv:
            save_failures = True
        if "--pass" in sys.argv:
            save_passed = True
        
        if parse_all_reports:
            print("Getting all allure report files from the directory: ", report_path)
            html_files = get_all_files(report_path)
            # Process each HTML file
            for html_file in html_files:
                html_file_path = os.path.join(report_path, html_file)
                summary = summarize_html_report(html_file_path)
                print(summary)

            # Create the processed_data.json file
            print("\nCreating processed_data.json...")
            sum_runtime_by_id_and_filename()

            # Remove duplicate IDs from the processed_data.json file
            print("\nRemoving duplicate IDs...")
            remove_duplicate_ids_keep_recent()
        else:
            summarize_allure_report(save_failures, save_passed)
            
            sum_runtime_by_id_and_filename()
            remove_duplicate_ids_keep_recent()
    except Exception as e:
        print(f"Error: {e}")
        exit(1)