import json
import logging
import os
from datetime import date, datetime, timedelta
import time
import traceback
import sys

CWD = os.path.dirname(__file__)
sys.path.append(os.path.join(CWD,'site-packages'))

from fpdf import FPDF
from fpdf.fonts import FontFace
from fpdf.enums import TableCellFillMode
from fpdf.drawing import Line


class Document(FPDF):
    RED = (255, 0, 0)
    PALE_RED = (255, 94, 94)
    GREEN = (0, 255, 0)
    PALE_GREEN = (167, 217, 173)
    WHITE = (255, 255, 255)
    BLACK = (0, 0, 0)

    def __init__(self, header_text=None, footer_text=None):
        super().__init__()
        self.chapter_no = 0
        self.subchapter_no = 0
        self.heading_sz = dict(enumerate((18, 16, 14, 12, 10), start=1))
        self.header_text = header_text
        self.footer_text = footer_text
        self.family = 'times'
        try:
            self.add_font('georgia', '', r"c:\WINDOWS\Fonts\georgia.ttf")
            self.add_font('georgia', 'B', r"c:\WINDOWS\Fonts\georgiab.ttf")
            self.add_font('georgia', 'I', r"c:\WINDOWS\Fonts\georgiai.ttf")
            self.add_font('georgia', 'BI', r"c:\WINDOWS\Fonts\georgiaz.ttf")
            self.family = 'georgia'
        except FileNotFoundError:
            pass
        self.reset_font()

    def reset_font(self):
        self.set_font(family=self.family, size=8)

    def header(self):
        if self.header_text is not None:

            # self.set_font(size=12)
            self.set_font(style='I', size=10)
            logo = os.path.join(CWD,'images','Leonardo_small.png')
            self.image(name=logo, x=10, y=14, w=30, keep_aspect_ratio=True)
            sw=self.get_string_width(s=self.header_text)
            self.text(x=(200 - sw), y=18, text=self.header_text)
            self.line(x1=10,y1=21,x2=200,y2=21)
            # self.cell(0, 10, border='B', align='L', link=self.image(r"c:\temp\Leonardo_small.png", w=30))
            # self.cell(0, 10, self.header_text, border='B', align='R')

            # with self.table() as table:
            #     row = table.row()
            #     row.cell(align='L', link=self.image(r"c:\temp\Leonardo_small.png", w=30))
            #     row.cell(self.header_text, align='C')

            self.ln(20)

    def footer(self):
        if self.footer_text is not None:
            self.set_y(-15)
            self.set_font(style='I', size=8)
            self.cell(0, 10, f'{self.footer_text} - Page {self.page_no()}', border='T', align='R')

    def add_chapter(self, title, newpage=True):
        self.chapter_no += 1
        self.subchapter_no = 0
        if newpage is True:
            self.add_page()
        self.add_heading(3, f'{self.chapter_no}   {title}', 'L')
        self.reset_font()

    def add_subchapter(self, title):
        self.subchapter_no += 1
        self.add_heading(4, f'{self.chapter_no}.{self.subchapter_no}   {title}', 'L')
        self.reset_font()

    def add_paragraph(self, text):
        self.multi_cell(0, 5, text, align='J')
        self.ln(10)

    def add_single_line(self, text, alignment='L', markdown=False):
        self.cell(0, 5, text, align=alignment, markdown=markdown)
        self.ln(10)

    def add_heading(self, level, text, alignment='L'):
        sz = self.heading_sz.get(level, 10)
        self.set_font(style='B', size=sz)
        self.cell(0, 10, text, border='0', align=alignment)
        self.ln(20)
        self.reset_font()


class PDFClass:
    def __init__(self, time='0'):
        self.vq1_equipment_info = {}
        self.radio_ip = ""
        self.radio_model = ""
        self.radio_sw_version = ""
        self.execution_time = time
        self.radio_serial_number = ""
        self.session_index = 1

    def start_doc(self, json_test: dict, head_foot: bool = True):
        """
        This function creates a latex doc object, sets its geometric option and other configurations.

        :param json_test:       the dict containing template with results and constants.
        :param head_foot:       if True, will add a head if header key is in json_test. Will do nothing otherwise.
        :return:                latex document.
        """
        header_text = json_test["header"] if head_foot else None
        footer_text = json_test["footer"] if head_foot else None
        doc = Document(header_text, footer_text)
        return doc

    def pdf_preamble(self, doc: Document, json_test: dict):
        """
        This function defines the preamble of a latex document

        :param doc:             latex document created by start_doc.
        :param json_test:       the dict containing template with results and constants.
        """
        test_title = json_test["title"]
        test_author = 'Test Report'
        today = datetime.today().strftime('%d/%m/%Y - %H:%M')
        doc.set_title(test_title)
        doc.set_author(test_author)
        doc.add_page()
        doc.ln(10)
        doc.add_heading(1, test_title, 'C')
        doc.set_font(size=12)
        doc.cell(0, 10, f'{test_author}', align='C')
        doc.ln(10)
        doc.cell(0, 10, f'{today}', align='C')
        doc.reset_font()

    def pdf_name(self, json_test: dict, new_path: str):
        """
        This creates a test case name depending on the current time.

        :param json_test:       json config file for the actual test case given by the function get_config.
        :param new_path:        path where the executer folder is located.
        :return:                file path.
        """
        logging.info("Setting PDF name")

        # Use test ID as base name (falls back to file_name) and include seconds
        now = datetime.now()
        date_name = now.strftime("%Y%m%d_%H%M%S")
        base = json_test.get('ID') or json_test.get('file_name') or 'testReport'
        # PDF will use suffix '_report' so final file is <base>_<timestamp>_report.pdf
        file_name_only = f"{base}_{date_name}_report"
        file_name = os.path.join(new_path, file_name_only)

        logging.info(f"Test: {file_name}")
        return file_name

    def pdf_test_information_section(self, doc: Document, json_test: dict):
        """
        This function gets the receiver configuration and builds the Test Information chapter.

        :param doc:             latex document created by start_doc.
        :param json_test:       the dict containing template with results and constants.
        """
        possible_info = {"radio_ip": "NaN", "radio_model": "NaN", "radio_sw_version": "NaN"}

        serial_number = "Not found"
        if self.radio_serial_number != "":
            serial_number = self.radio_serial_number
        rows = [
            ('**ID**', json_test.get("ID", ''), '**Requirement**', json_test.get("requirement", '')),
            ('**Strategy**', json_test.get("strategy", ''), '**Technique**', json_test.get("technique", '')),
            ('**Level**', json_test.get("level", ''), '**Setup**', json_test.get("setup", '')),
            ('**Author**', json_test.get("author", ''), '**Reviewer**', json_test.get("reviewer", '')),
            ('**Equipment SN**', serial_number, '**Execution Time**', f'{self.execution_time}')
        ]

        # If the test JSON contains a script version (injected by the runner), show it
        if 'script_version' in json_test:
            try:
                rows.append(('**Script Version**', json_test.get('script_version', ''), '', ''))
            except Exception:
                pass
        rows = tuple(rows)

        doc.ln(40)
        doc.add_chapter('Test Information', False)
        doc.set_font(size=10)

        with doc.table(
                borders_layout="INTERNAL",
                col_widths=(60, 60, 60, 60),
                markdown=True,
                headings_style=FontFace(),
                line_height=6,
                text_align=("LEFT", "CENTER", "LEFT", "CENTER"),
                width=180) as table:
            for data_row in rows:
                table.row(data_row)

            if "$" not in json_test["description"]:
                logging.info("Creating row description")
                row = table.row()
                row.cell("**Description**")
                row.cell(json_test["description"], colspan=3)

        doc.ln(10)

    def pdf_step_execution(self, step_list: list, doc: Document):
        try:
            logging.info("Writing step results")

            doc.ln(10)
            doc.add_subchapter('Step execution and results')
            doc.set_font(size=9)

            name_step = 'Step '
            step_nb = 0

            with doc.table(
                    borders_layout="INTERNAL",
                    col_widths=(30, 90, 60, 60),
                    headings_style=FontFace(emphasis="BOLD", size_pt=10),
                    text_align=("LEFT", "LEFT", "LEFT", "CENTER"),
                    width=180) as table:

                table.row(('Step', 'Description', 'Result', 'Error'))

                for step in step_list:
                    step_nb = step_nb + 1
                    description = step["desc"]
                    naming = name_step + str(step_nb)

                    err_result = ""
                    ex_result = ""

                    color = doc.WHITE
                    if (step.get("result"), step.get("expected")) == (None, None):
                        color = doc.WHITE
                        naming = ''
                        step_nb = step_nb - 1
                    else:
                        if not step.get("result"):
                            color = doc.PALE_RED
                        elif step.get("expected"):
                            color = doc.PALE_GREEN

                    style = FontFace(fill_color=color)

                    if step.get("expected"):
                        ex_result = step["expected"].replace("var_", "", 1)

                    if step.get("error"):
                        err_result = str(step["error"])  # step["error"].replace("var_", "", 1)

                    row = table.row()
                    for c in (naming, description, ex_result, err_result):
                        row.cell(c, style=style)

            doc.ln(10)
            return True
        except Exception as e:
            logging.error(e)
            logging.exception("Error message:")
            traceback.print_exc()
            return False

    def pdf_step_result(self, name, passed, failure, doc: Document):
        tot = passed + failure
        fail = 0
        if tot > 0:
            fail = 100 * failure / tot
        session_name = 'Test {}: {}'.format(self.session_index, name)
        self.session_index = self.session_index + 1

        if failure > 0:
            line_color = doc.PALE_RED
        else:
            line_color = doc.PALE_GREEN

        rows = (('Total STEP', 'PASS', 'FAIL', '% of Failure'),
                (str(tot), str(passed), str(failure), '{:.02f}'.format(fail))
                )

        doc.add_chapter(session_name)
        doc.set_font(size=9)

        with doc.table(
                borders_layout="INTERNAL",
                cell_fill_color=line_color,
                cell_fill_mode=TableCellFillMode.ROWS,
                col_widths=(60, 60, 60, 60),
                # headings_style=FontFace(emphasis="BOLD", size_pt=10, color=234, fill_color=(128, 128, 128)),
                headings_style=FontFace(emphasis="BOLD", size_pt=10),
                line_height=6,
                text_align=("CENTER", "CENTER", "CENTER", "CENTER"),
                width=180) as table:
            for data_row in rows:
                table.row(data_row)
        return True

    def pdf_step_summary(self, passed, failure, doc: Document):
        tot = passed + failure
        fail = 100 * failure / tot
        session_name = 'Test Summary'

        if failure > 0:
            line_color = doc.PALE_RED
        else:
            line_color = doc.PALE_GREEN

        rows = (('Total TEST', 'PASS', 'FAIL', '% of Failure'),
                (str(tot), str(passed), str(failure), '{:.02f}'.format(fail))
                )

        doc.add_chapter(session_name)
        doc.set_font(size=9)

        with doc.table(
                borders_layout="INTERNAL",
                cell_fill_color=line_color,
                cell_fill_mode=TableCellFillMode.ROWS,
                col_widths=(60, 60, 60, 60),
                # headings_style=FontFace(),  # FontFace(emphasis="BOLD", color=234, fill_color=(128, 128, 128)),
                headings_style=FontFace(emphasis="BOLD", size_pt=10),
                line_height=6,
                text_align=("CENTER", "CENTER", "CENTER", "CENTER"),
                width=180) as table:
            for data_row in rows:
                table.row(data_row)

        return True

    def pdf_add_image(self, name, title=None, alt_text=None, width=0, height=0):
        # self.add_page()
        doc.image(name=name, title=title, alt_text=alt_text, keep_aspect_ratio=True, w=width, h=height)

    def pdf_add_per_run_summary(self, doc: Document, repetitions: list):
        """
        Generate professional per-run summary table with both 1553 and Serial statistics.
        
        Creates a dedicated chapter with comprehensive per-run metrics including:
        - Run status (PASS/FAIL)
        - PBIT timing
        - B6 status checks (Pass/Fail/Known)
        - B8 diagnostic checks
        - Serial communication (Errors/Fatal/Recycles)
        
        Args:
            doc: Document object for PDF generation
            repetitions: List of per-run statistics dictionaries with keys:
                - 'repetition': Run number
                - 'success': Boolean pass/fail
                - 'pbit_time': BIT completion time in seconds
                - 'b6_pass', 'b6_fail', 'b6_known_fail': B6 check counts
                - 'b8_checked', 'b8_pass', 'b8_fail': B8 check counts  
                - 'serial_total', 'serial_errors', 'serial_fatal', 'serial_recycles': Serial stats
        
        This table appears as a standalone chapter, NOT mixed with step execution logs.
        """
        doc.add_chapter('Per-Run Summary (1553 + Serial Statistics)')
        doc.ln(5)
        doc.set_font(size=9)
        
        # Table header row (added Target column)
        headers = ('Run', 'Status', 'PBIT\nTime', 'B6 Status\n(P/F/K)', 
               'B8 Checks\n(P/F/K)', 'Serial Messages\n(E/F/R)', 'Target', 'Target Test')
        
        with doc.table(
            borders_layout="ALL",
            col_widths=(15, 20, 20, 30, 30, 30, 20, 20),
                headings_style=FontFace(emphasis="BOLD", size_pt=9, fill_color=(200, 200, 200)),
                line_height=5,
                text_align=("CENTER", "CENTER", "CENTER", "CENTER", "CENTER", "CENTER", "CENTER", "CENTER"),
                width=180) as table:
            
            table.row(headers)
            
            for run in repetitions:
                # Determine row color based on result
                if run['success']:
                    row_color = doc.PALE_GREEN
                    status_text = "PASS"
                    result_symbol = "✓"
                else:
                    row_color = doc.PALE_RED
                    status_text = "FAIL"
                    result_symbol = "✗"
                
                style = FontFace(fill_color=row_color, size_pt=9)
                
                # Format data compactly
                pbit_time = f"{run['pbit_time']:.1f}s" if run.get('bit_available', True) else "N/A"
                b6_status = f"{run['b6_pass']}/{run['b6_fail']}/{run['b6_known_fail']}"
                b8_checks = f"{run['b8_pass']}/{run['b8_fail']}/{run.get('b8_known_fail',0)}" if run['b8_checked'] > 0 else "-"
                serial_stats = f"{run.get('serial_errors', 0)}/{run.get('serial_fatal', 0)}/{run.get('serial_recycles', 0)}"
                result_text = f"{result_symbol} {status_text}"
                
                row = table.row()
                # Format target cell - only show data if target test was actually performed
                target_detected = run.get('target_detected')
                if target_detected is None:
                    # Target test was not performed (run_on_target=False)
                    target_text = '-'
                    target_result_text = '-'
                else:
                    # Target test was performed - show results based on detection
                    if target_detected:
                        # PASS: target was detected - show details (distance @ cycle)
                        t_info = run.get('target_simulated')
                        if t_info:
                            try:
                                target_text = f"{t_info.get('distance')}m @ cycle {t_info.get('appeared_after_cycles', t_info.get('found_at_iter','?'))}"
                            except Exception:
                                target_text = "Detected"
                        else:
                            target_text = "Detected"
                        target_result_text = "PASS"
                    else:
                        # FAIL: target was NOT detected - no details to show
                        target_text = '-'
                        target_result_text = "FAIL"

                for cell_text in (str(run['repetition']), status_text, pbit_time, 
                                 b6_status, b8_checks, serial_stats, target_text, target_result_text):
                    row.cell(cell_text, style=style)
        
        doc.ln(5)
        doc.set_font(size=8, style='I')
        doc.multi_cell(0, 4, 
            "Legend: B6 Status = Pass/Fail/Known (known failures ignored) | "
            "B8 Checks = Pass/Fail/Known (known failures ignored) | "
            "Serial = Errors(%E)/Fatal(%F)/Recycles | "
            "Target Test = Target detection result (PASS/FAIL)", 
            align='L')
        doc.reset_font()

    def pdf_add_global_statistics(self, doc: Document, aggregate: dict):
        """
        Generate professional global aggregate statistics table.
        
        Creates a dedicated chapter with combined statistics from all test runs:
        - Overall test summary (total runs, pass/fail rates)
        - Aggregate 1553 statistics (B6/B8 checks across all runs)
        - Aggregate serial statistics (total messages, errors, fatal, recycles)
        - PBIT timing analysis (avg, min, max, std dev)
        
        Args:
            doc: Document object for PDF generation
            aggregate: Dictionary with aggregate statistics:
                - 'total_runs', 'successful_runs', 'failed_runs'
                - 'total_b6_checks', 'total_b6_pass', 'total_b6_fail', 'total_b6_known'
                - 'total_b8_checks', 'total_b8_pass', 'total_b8_fail'
                - 'total_serial_msgs', 'total_serial_errors', 'total_serial_fatal', 'total_serial_recycles'
                - 'avg_pbit_time', 'min_pbit_time', 'max_pbit_time', 'std_dev_pbit'
        
        This table appears as a standalone chapter, NOT mixed with step execution logs.
        """
        doc.add_chapter('Global Aggregate Statistics (All Runs Combined)')
        doc.ln(5)
        
        # Section 1: Overall Test Summary
        doc.add_subchapter('Overall Test Summary')
        doc.set_font(size=9)
        
        total = aggregate['total_runs']
        success = aggregate['successful_runs']
        failed = aggregate['failed_runs']
        success_rate = (success / total * 100) if total > 0 else 0
        
        rows_summary = (
            ('Metric', 'Count', 'Percentage'),
            ('Total Runs', str(total), '100%'),
            ('Successful Runs', str(success), f"{success_rate:.1f}%"),
            ('Failed Runs', str(failed), f"{100-success_rate:.1f}%")
        )
        
        with doc.table(
                borders_layout="ALL",
                col_widths=(60, 60, 60),
                headings_style=FontFace(emphasis="BOLD", size_pt=9, fill_color=(200, 200, 200)),
                line_height=5,
                text_align=("LEFT", "CENTER", "CENTER"),
                width=180) as table:
            for data_row in rows_summary:
                table.row(data_row)
        
        doc.ln(10)
        
        # Section 2: 1553 Bus Statistics (B6 + B8)
        doc.add_subchapter('1553 Bus Statistics')
        doc.set_font(size=9)
        
        total_b6 = aggregate['total_b6_checks']
        b6_pass_rate = (aggregate['total_b6_pass'] / total_b6 * 100) if total_b6 > 0 else 0
        b6_fail_rate = (aggregate['total_b6_fail'] / total_b6 * 100) if total_b6 > 0 else 0
        b6_known_rate = (aggregate['total_b6_known'] / total_b6 * 100) if total_b6 > 0 else 0
        
        rows_b6 = (
            ('Metric', 'Count', 'Percentage'),
            ('B6 LRU Status - Total Checks', str(total_b6), '100%'),
            ('B6 - Pass', str(aggregate['total_b6_pass']), f"{b6_pass_rate:.1f}%"),
            ('B6 - Fail (Real)', str(aggregate['total_b6_fail']), f"{b6_fail_rate:.1f}%"),
            ('B6 - Known Failures (Ignored)', str(aggregate['total_b6_known']), f"{b6_known_rate:.1f}%")
        )
        
        with doc.table(
                borders_layout="ALL",
                col_widths=(80, 50, 50),
                headings_style=FontFace(emphasis="BOLD", size_pt=9, fill_color=(200, 200, 200)),
                line_height=5,
                text_align=("LEFT", "CENTER", "CENTER"),
                width=180) as table:
            for data_row in rows_b6:
                table.row(data_row)
        
        doc.ln(5)
        
        # B8 statistics (if any checks were performed)
        total_b8 = aggregate['total_b8_checks']
        if total_b8 > 0:
            b8_pass_rate = (aggregate['total_b8_pass'] / total_b8 * 100)
            b8_fail_rate = (aggregate['total_b8_fail'] / total_b8 * 100)
            
            b8_known = aggregate.get('total_b8_known', 0)
            b8_known_rate = (b8_known / total_b8 * 100) if total_b8 > 0 else 0

            rows_b8 = (
                ('Metric', 'Count', 'Percentage'),
                ('B8 Diagnostic - Total Checks', str(total_b8), '100%'),
                ('B8 - Pass', str(aggregate['total_b8_pass']), f"{b8_pass_rate:.1f}%"),
                ('B8 - Fail', str(aggregate['total_b8_fail']), f"{b8_fail_rate:.1f}%"),
                ('B8 - Known Failures (Ignored)', str(b8_known), f"{b8_known_rate:.1f}%")
            )
            
            with doc.table(
                    borders_layout="ALL",
                    col_widths=(80, 50, 50),
                    headings_style=FontFace(emphasis="BOLD", size_pt=9, fill_color=(200, 200, 200)),
                    line_height=5,
                    text_align=("LEFT", "CENTER", "CENTER"),
                    width=180) as table:
                for data_row in rows_b8:
                    table.row(data_row)
            
            doc.ln(10)
        else:
            doc.set_font(size=9, style='I')
            doc.cell(0, 5, 'B8 Diagnostic checks: Not performed (no real B6 failures detected)', align='L')
            doc.ln(10)
            doc.reset_font()
        
        # Section 3: Serial Communication Statistics
        doc.add_subchapter('Serial Communication Statistics')
        doc.set_font(size=9)
        
        total_serial = aggregate['total_serial_msgs']
        serial_error_rate = (aggregate['total_serial_errors'] / total_serial * 100) if total_serial > 0 else 0
        serial_fatal_rate = (aggregate['total_serial_fatal'] / total_serial * 100) if total_serial > 0 else 0
        
        rows_serial = (
            ('Metric', 'Count', 'Notes'),
            ('Total Serial Messages', str(total_serial), 'All messages processed'),
            ('Error Messages (%%E)', str(aggregate['total_serial_errors']), f"{serial_error_rate:.1f}% of total"),
            ('Fatal Messages (%%F)', str(aggregate['total_serial_fatal']), f"{serial_fatal_rate:.1f}% of total"),
            ('System Recycles', str(aggregate['total_serial_recycles']), 'Power cycles + unexpected resets')
        )
        
        with doc.table(
                borders_layout="ALL",
                col_widths=(70, 40, 70),
                headings_style=FontFace(emphasis="BOLD", size_pt=9, fill_color=(200, 200, 200)),
                line_height=5,
                text_align=("LEFT", "CENTER", "LEFT"),
                width=180) as table:
            for data_row in rows_serial:
                table.row(data_row)
        
        doc.ln(10)
        
        # Section 4: PBIT Timing Analysis
        if 'avg_pbit_time' in aggregate and aggregate['avg_pbit_time'] is not None:
            doc.add_subchapter('PBIT Timing Analysis')
            doc.set_font(size=9)
            
            rows_timing = (
                ('Metric', 'Value', 'Unit'),
                ('Average PBIT Time', f"{aggregate['avg_pbit_time']:.2f}", 'seconds'),
                ('Minimum PBIT Time', f"{aggregate['min_pbit_time']:.2f}", 'seconds'),
                ('Maximum PBIT Time', f"{aggregate['max_pbit_time']:.2f}", 'seconds'),
                ('Standard Deviation', f"{aggregate['std_dev_pbit']:.2f}", 'seconds')
            )
            
            with doc.table(
                    borders_layout="ALL",
                    col_widths=(70, 50, 60),
                    headings_style=FontFace(emphasis="BOLD", size_pt=9, fill_color=(200, 200, 200)),
                    line_height=5,
                    text_align=("LEFT", "CENTER", "CENTER"),
                    width=180) as table:
                for data_row in rows_timing:
                    table.row(data_row)
            
            doc.ln(5)
            doc.set_font(size=8, style='I')
            doc.multi_cell(0, 4, 
                "PBIT timing measures the duration from power-on to BIT report availability. "
                "Consistent timing indicates stable system behavior.", 
                align='L')
            doc.reset_font()

    def pdf_add_machine_reliability_assessment(self, doc: Document, aggregate: dict, repetitions: list):
        """
        Generate Machine Reliability Assessment with problem distribution table.
        
        Creates a dedicated chapter analyzing machine reliability with:
        - Overall reliability percentage (perfect runs vs total)
        - Problem type distribution table ordered by frequency
        - Clear identification of main issues to address
        
        Args:
            doc: Document object for PDF generation
            aggregate: Dictionary with aggregate statistics
            repetitions: List of per-run statistics to analyze problem types
        
        This section provides executive-level reliability assessment for maintenance decisions.
        """
        doc.add_chapter('Machine Reliability Assessment')
        doc.ln(5)
        
        # Calculate overall reliability
        total = aggregate['total_runs']
        perfect_runs = aggregate['successful_runs']
        reliability_pct = (perfect_runs / total * 100) if total > 0 else 0
        
        doc.set_font(size=10)
        doc.multi_cell(0, 5, 
            f"This section provides an executive summary of the machine under test, "
            f"analyzing {total} complete test cycles to identify reliability metrics and problem patterns.",
            align='J')
        doc.ln(5)
        
        # Overall Reliability Box
        doc.add_subchapter('Overall Reliability Score')
        doc.set_font(size=9)
        
        if reliability_pct >= 80:
            reliability_color = doc.PALE_GREEN
            assessment = "EXCELLENT"
        elif reliability_pct >= 60:
            reliability_color = (255, 255, 200)  # Pale yellow
            assessment = "GOOD"
        elif reliability_pct >= 40:
            reliability_color = (255, 200, 150)  # Pale orange
            assessment = "ACCEPTABLE"
        else:
            reliability_color = doc.PALE_RED
            assessment = "CRITICAL"
        
        rows_reliability = (
            ('Metric', 'Value', 'Assessment'),
            ('Perfect Runs (No Issues)', f"{perfect_runs}/{total}", f"{reliability_pct:.1f}%"),
            ('Runs with Issues', f"{total - perfect_runs}/{total}", f"{100 - reliability_pct:.1f}%"),
            ('Overall Rating', assessment, '')
        )
        
        with doc.table(
                borders_layout="ALL",
                col_widths=(70, 50, 60),
                headings_style=FontFace(emphasis="BOLD", size_pt=9, fill_color=(200, 200, 200)),
                line_height=5,
                text_align=("LEFT", "CENTER", "CENTER"),
                width=180) as table:
            for idx, data_row in enumerate(rows_reliability):
                row = table.row()
                for cell_text in data_row:
                    if idx == len(rows_reliability) - 1:  # Last row (rating)
                        row.cell(cell_text, style=FontFace(fill_color=reliability_color, emphasis="BOLD", size_pt=9))
                    else:
                        row.cell(cell_text)
        
        doc.ln(10)
        
        # Known Failures Information Box
        doc.add_subchapter('Known Failures (Excluded from Statistics)')
        doc.set_font(size=9)
        
        # Import KNOWN_FAILURES from test script
        try:
            import sys
            import os
            # Add scripts folder to path to import KNOWN_FAILURES
            script_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts')
            if script_dir not in sys.path:
                sys.path.insert(0, script_dir)
            from GRIFO_M_PBIT import KNOWN_FAILURES
            
            doc.multi_cell(0, 5,
                "The following test checks are expected to fail due to hardware test setup limitations "
                "(e.g., missing physical components). These failures are tracked separately and do not "
                "affect the test verdict or reliability calculations:",
                align='J')
            doc.ln(3)
            
            # List known failures
            doc.set_font(size=9)
            for known_field in KNOWN_FAILURES:
                # Extract clean field name
                if 'RdrHealthStatusAndBitReport_' in known_field:
                    clean_name = known_field.split('RdrHealthStatusAndBitReport_')[-1]
                else:
                    clean_name = known_field.split('_')[-1] if '_' in known_field else known_field
                clean_name = clean_name.replace('_', ' ').title()
                doc.cell(10, 5, '')
                doc.cell(0, 5, f"• {clean_name}", ln=True)
        except ImportError:
            doc.multi_cell(0, 5,
                "Note: Known failures configuration could not be loaded.",
                align='J')
        
        doc.ln(10)
        
        # Problem Distribution Analysis
        doc.add_subchapter('Problem Distribution Analysis')
        doc.set_font(size=9)
        
        # Analyze problem types from repetitions
        problem_counts = {}
        
        for run in repetitions:
            if not run['success']:
                # Extract FULL field names from failures (not just last parts)
                for field, value in run['failures']:
                    # Remove common prefix but keep full field identifier
                    # Example: "radar_health_status_and_bit_report_valid_RdrHealthStatusAndBitReport_processor_status"
                    #       -> "processor_status"
                    if 'RdrHealthStatusAndBitReport_' in field:
                        # Extract everything after message name
                        test_name = field.split('RdrHealthStatusAndBitReport_')[-1]
                    elif '_' in field and len(field.split('_')) > 3:
                        # For other messages, keep last 4 parts for context
                        parts = field.split('_')
                        test_name = '_'.join(parts[-4:])
                    else:
                        test_name = field
                    
                    # Clean up for display (capitalize, keep underscores for clarity)
                    test_name = test_name.replace('_', ' ').title()
                    
                    # Count this specific test failure
                    problem_counts[test_name] = problem_counts.get(test_name, 0) + 1
                
                # Also check serial problems
                if run.get('serial_fatal', 0) > 0:
                    problem_counts['Serial Communication (Fatal)'] = problem_counts.get('Serial Communication (Fatal)', 0) + 1
                if run.get('serial_recycles', 0) > 1:  # More than expected
                    problem_counts['System Instability (Recycles)'] = problem_counts.get('System Instability (Recycles)', 0) + 1
        
        if problem_counts:
            # Sort by frequency (descending)
            sorted_problems = sorted(problem_counts.items(), key=lambda x: x[1], reverse=True)
            
            doc.multi_cell(0, 5,
                f"Analysis of {total - perfect_runs} runs with issues reveals the following problem distribution:",
                align='J')
            doc.ln(5)
            
            # Problem distribution table
            headers = ('Problem Type', 'Occurrences', '% of Total Runs', '% of Failed Runs')
            
            with doc.table(
                    borders_layout="ALL",
                    col_widths=(70, 35, 37.5, 37.5),
                    headings_style=FontFace(emphasis="BOLD", size_pt=9, fill_color=(200, 200, 200)),
                    line_height=5,
                    text_align=("LEFT", "CENTER", "CENTER", "CENTER"),
                    width=180) as table:
                
                table.row(headers)
                
                for problem, count in sorted_problems:
                    pct_total = (count / total * 100)
                    pct_failed = (count / (total - perfect_runs) * 100) if (total - perfect_runs) > 0 else 0
                    
                    # Color code by severity
                    if pct_total >= 20:
                        row_color = doc.PALE_RED
                    elif pct_total >= 10:
                        row_color = (255, 200, 150)  # Orange
                    else:
                        row_color = (255, 255, 200)  # Yellow
                    
                    style = FontFace(fill_color=row_color, size_pt=9)
                    
                    row = table.row()
                    row.cell(problem, style=style)
                    row.cell(f"{count}/{total}", style=style)
                    row.cell(f"{pct_total:.1f}%", style=style)
                    row.cell(f"{pct_failed:.1f}%", style=style)
            
            doc.ln(5)
            doc.set_font(size=8, style='I')
            doc.multi_cell(0, 4,
                "Legend: Red (≥20% of runs) = Critical priority | Orange (≥10%) = High priority | Yellow (<10%) = Medium priority",
                align='L')
            doc.reset_font()
            
        else:
            doc.set_font(size=10, style='B')
            doc.cell(0, 5, '✓ NO PROBLEMS DETECTED - All runs completed successfully', align='L')
            doc.ln(10)
            doc.reset_font()

    def pdf_generate(self, folder_path: str, doc: Document, file_name: str):
        """
        This function generates a pdf. Usually the folder_path + file_name should have less than 260 characters.

        :param folder_path:         path where the PDF will be created.
        :param doc:                 latex document created by start_doc.
        :param file_name:           name of the PDF.
        """

        file_path = os.path.join(folder_path, file_name)
        logging.info(f"Generating pdf file {file_path} and its length is {len(file_path)}")
        try:
            doc.output(f'{file_path}.pdf')
        except Exception as e:
            logging.warning("PDF not created for this reason: {}".format(e))


if __name__ == '__main__':
    f = open('..\\rt800_template.json')
    t = open('..\\RBT_BLACK_ATC_1783.json')
    json_template = json.load(f)
    json_test = json.load(t)

    my_pdf = PDFClass()

    doc = my_pdf.start_doc(json_template)
    file_name = my_pdf.pdf_name(json_template, '')

    my_pdf.pdf_preamble(doc, json_test)
    my_pdf.pdf_test_information_section(doc, json_test)
    # my_pdf.pdf_add_image(r'C:\temp\prova.png', 'Prova', 'provax?')
    my_pdf.pdf_step_summary(7, 3, doc)
    my_pdf.pdf_step_summary(3, 1, doc)
    my_pdf.pdf_step_summary(10, 0, doc)
    my_pdf.pdf_step_summary(0, 3, doc)

    my_pdf.pdf_step_result('HF - USB Sensitivity - cycle number 1', 17, 0, doc)

    step_values = [['Start Time: 2024-03-14 16:02:12.199903'],
                   ['Get VQ1MPlatform-MIB::current Waveform', 'DCE:46c8c5bb-d957-4bd8-9be9-98beb34ee888','DCE:46c8c5bb-d957-4bd8-9be9-98beb34ee888'],
                   ['Get VQ1MPlatform-MIB::current Waveform', None, 'DCE:46c8c5bb-d957-4bd8-9be9-98beb34ee888', 'ERROR!'],
                   ['Set HFSuite-MIB::operationalMode 1.0 to 0', '0', '0'],
                   ['Set HFSuite-MIB::rxSquelch1.0 to 0', '0', '0'],
                   ['Set HFSuite-MIB::rfModulation Type1.0 to 0', '0', '0'],
                   ['Set cma180_set_connector to OutputConnector.RFCom', 'OutputConnector.RFCom', 'OutputConnector.RFCom'],
                   ['Set cma180_set_rf_freq to 2000000', '2000000', '2000000'],
                   ['Set cma180_set_level to-111', '-111', '-111'],
                   ['Set HFSuite-MIB::rxSquelch1.0 to 0', '0', '0'],
                   ['Set HFSuite-MIB::rfModulation Type1.0 to 0', '0', '0'],
                   ['Set cma180_set_connector to OutputConnector.RFCom', 'OutputConnector.RFCom', 'OutputConnector.RFCom'],
                   ['Set cma180_set_rf_freq to 2000000', '2000000', '2000000'],
                   ['Set cma180_set_level to-111', '-111', '-111'],
                   ]

    step_keys = ['desc', 'result', 'expected', 'error']
    step_list = []
    for v in step_values:

        d = dict(zip(step_keys, v))
        step_list.append(d)

    my_pdf.pdf_step_execution(step_list, doc)

    my_pdf.pdf_generate('', doc, file_name)
