#!/usr/bin/env python3

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
Script to generate an HTML report for flaky tests detected in JUnit XML results.

This script processes JUnit test results generated by Flank and Firebase Test Lab,
specifically looking for tests marked with the flaky="true" attribute. It creates
a styled HTML report with links to Firebase Test Lab for detailed test analysis.

- Searches for the FullJUnitReport.xml file in the results directory.
- Identifies test cases with flaky="true" attribute.
- Extracts failure information and Firebase Test Lab web links.
- Generates a formatted HTML report with clickable links to test executions.
- Only creates the report if flaky tests are found.
- Designed for use in Taskcluster following a Firebase Test Lab test execution.

Flank: https://flank.github.io/flank/

Usage:
    python3 generate-flaky-report-from-ftl.py --results <path_to_results_directory>
    python3 generate-flaky-report-from-ftl.py --results <path_to_results_directory>
      --output <path_to_output_html>
"""

import argparse
import logging
import sys
from pathlib import Path
from typing import Any

from junitparser import Attr, JUnitXml, TestCase


class test_case(TestCase):
    flaky = Attr()


def setup_logging():
    """Configure logging for the script."""
    log_format = "%(levelname)s: %(message)s"
    logging.basicConfig(level=logging.INFO, format=log_format)


def find_junit_xml_files(results_dir: Path) -> list[Path]:
    """Find the FullJUnitReport.xml file in the results directory.

    Args:
        results_dir: Path to the results directory

    Returns:
        List containing path to FullJUnitReport.xml if it exists
    """
    full_report = results_dir / "FullJUnitReport.xml"
    if full_report.exists():
        logging.info(f"Found FullJUnitReport.xml in {results_dir}")
        return [full_report]
    else:
        logging.info(f"FullJUnitReport.xml not found in {results_dir}")
        return []


def extract_flaky_tests(xml_files: list[Path]) -> list[dict[str, Any]]:
    """Parse JUnit XML files and extract tests marked as flaky.

    Args:
        xml_files: List of paths to JUnit XML files

    Returns:
        List of dictionaries containing flaky test information
    """
    flaky_tests = []

    for xml_file in xml_files:
        try:
            xml = JUnitXml.fromfile(str(xml_file))
            for suite in xml:
                for case in suite:
                    # Use custom test_case class to access flaky attribute
                    cur_case = test_case.fromelem(case)
                    if isinstance(cur_case, TestCase) and cur_case.flaky:
                        flaky_info = {
                            "name": cur_case.name,
                            "classname": cur_case.classname,
                            "time": cur_case.time,
                            "file": xml_file.name,
                        }

                        # Extract webLink if present
                        web_link = cur_case._elem.find("webLink")
                        if web_link is not None and web_link.text:
                            flaky_info["weblink"] = web_link.text

                        # Capture failure/error information if present
                        if cur_case.result:
                            result_info = []
                            for result in cur_case.result:
                                result_info.append(
                                    {
                                        "type": (
                                            result.type
                                            if result.type
                                            else "Failure Stack Trace"
                                        ),
                                        "message": (
                                            result.message
                                            if result.message
                                            else result.text
                                        ),
                                    }
                                )
                            flaky_info["results"] = result_info

                        flaky_tests.append(flaky_info)
                        logging.info(
                            f"Found flaky test: {cur_case.classname}.{cur_case.name}"
                        )

        except Exception as e:
            logging.error(f"Failed to parse {xml_file}: {e}")
            continue

    return flaky_tests


def generate_html_report(flaky_tests: list[dict[str, Any]], output_path: Path) -> None:
    """Generate an HTML report for flaky tests.

    Args:
        flaky_tests: List of flaky test information
        output_path: Path where the HTML report should be written
    """
    html_content = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flaky Test Report</title>
    <style>
        body {{
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            line-height: 1.6;
            color: #333;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }}
        h1 {{
            color: #e65100;
            border-bottom: 3px solid #e65100;
            padding-bottom: 10px;
        }}
        .summary {{
            background-color: #fff3e0;
            border-left: 4px solid #ff9800;
            padding: 15px;
            margin: 20px 0;
            border-radius: 4px;
        }}
        .test-case {{
            background-color: white;
            border: 1px solid #ddd;
            border-radius: 4px;
            padding: 15px;
            margin: 15px 0;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }}
        .test-name {{
            font-size: 1.1em;
            font-weight: bold;
            color: #d84315;
            margin-bottom: 8px;
        }}
        .test-class {{
            color: #666;
            font-size: 0.95em;
            margin-bottom: 8px;
        }}
        .test-meta {{
            color: #888;
            font-size: 0.9em;
            margin-bottom: 10px;
        }}
        .firebase-link {{
            display: inline-block;
            background-color: #1976d2;
            color: white;
            padding: 8px 16px;
            text-decoration: none;
            border-radius: 4px;
            margin-top: 10px;
            font-size: 0.9em;
        }}
        .firebase-link:hover {{
            background-color: #1565c0;
        }}
        .result-info {{
            background-color: #ffebee;
            border-left: 3px solid #f44336;
            padding: 10px;
            margin-top: 10px;
            font-family: monospace;
            font-size: 0.9em;
            white-space: pre-wrap;
            word-wrap: break-word;
        }}
        .result-type {{
            font-weight: bold;
            color: #c62828;
        }}
        .footer {{
            margin-top: 30px;
            padding-top: 20px;
            border-top: 1px solid #ddd;
            text-align: center;
            color: #666;
            font-size: 0.9em;
        }}
    </style>
</head>
<body>
    <h1>⚠️ Flaky Test Report</h1>

    <div class="summary">
        <strong>Total Flaky Tests Found:</strong> {len(flaky_tests)}
    </div>
"""

    for test in flaky_tests:
        html_content += f"""
    <div class="test-case">
        <div class="test-name">{test['name']}</div>
        <div class="test-class">Class: {test['classname']}</div>
        <div class="test-meta">
            Execution Time: {test.get('time', 'N/A')}s | Source: {test['file']}
        </div>
"""

        if "weblink" in test:
            html_content += f"""
        <a href="{test['weblink']}" class="firebase-link" target="_blank">View in Firebase Test Lab</a>
"""

        if "results" in test:
            for result in test["results"]:
                html_content += f"""
        <div class="result-info">
            <div class="result-type">{result.get('type', 'Unknown')}</div>
            <div>{result.get('message', 'No message available')}</div>
        </div>
"""

        html_content += """
    </div>
"""

    html_content += """
    <div class="footer">
        Generated by Mozilla Mobile Test Engineering
    </div>
</body>
</html>
"""

    output_path.write_text(html_content, encoding="utf-8")
    logging.info(f"HTML flaky report written to {output_path}")


def main():
    """Parse arguments and generate flaky test report."""
    parser = argparse.ArgumentParser(
        description="Generate an HTML report for flaky tests from JUnit XML results"
    )
    parser.add_argument(
        "--results",
        required=True,
        help="Path to the results directory containing JUnit XML files",
    )
    parser.add_argument(
        "--output",
        default="/builds/worker/artifacts/HtmlFlakyReport.html",
        help="Path where the HTML report should be written",
    )
    args = parser.parse_args()

    results_dir = Path(args.results)
    output_path = Path(args.output)

    if not results_dir.exists():
        logging.warning(f"Results directory does not exist: {results_dir}")
        sys.exit(0)

    # Find all JUnit XML files
    xml_files = find_junit_xml_files(results_dir)

    if not xml_files:
        logging.info("No JUnit XML files found, skipping flaky report generation")
        sys.exit(0)

    # Extract flaky tests
    flaky_tests = extract_flaky_tests(xml_files)

    if not flaky_tests:
        logging.info("No flaky tests detected, skipping report generation")
        sys.exit(0)

    # Generate HTML report
    output_path.parent.mkdir(parents=True, exist_ok=True)
    generate_html_report(flaky_tests, output_path)

    logging.info(
        f"Flaky test report generation complete: {len(flaky_tests)} flaky tests found"
    )
    sys.exit(0)


if __name__ == "__main__":
    setup_logging()
    main()
