from __future__ import annotations

import os
from datetime import datetime
from typing import Dict, List, Optional

from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import (Image, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle)

try:
    from .i18n import DEFAULT_LANGUAGE, TRANSLATIONS, get_translator  # type: ignore
except ImportError:  # Standalone execution
    from i18n import DEFAULT_LANGUAGE, TRANSLATIONS, get_translator  # type: ignore


from .app_console import fc_log
from .pdf_pagination_utils import (
    cond_page_break_min_remaining_height,
    cond_page_break_before_footer_block,
)
def _format_number(value: Optional[float], suffix: str = "") -> str:
    try:
        if value is None:
            return "-"
        return f"{float(value):.2f}{suffix}"
    except (TypeError, ValueError):
        return str(value) if value is not None else "-"


def _load_logo_if_available(story: List, size: float = 1.5 * inch) -> None:
    """Adiciona o logotipo da FastTag caso o arquivo exista."""
    try:
        root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
        logo_path = os.path.join(root, "assets", "images", "fasttag_logo.png")
        if os.path.exists(logo_path):
            story.append(Image(logo_path, width=size, height=size))
            story.append(Spacer(1, 10))
    except Exception as exc:  # pragma: no cover - fallback silencioso
        fc_log("orientation_report", f"[AVISO] Não foi possível carregar o logo: {exc}")


def _get_system_info(language_code: Optional[str] = None) -> Dict[str, str]:
    """Coleta informações gerais para o cabeçalho do relatório."""
    translator = get_translator()
    lang = language_code or translator.get_language()
    lc = 'pt' if str(lang).startswith('pt') else 'en'
    bundle = TRANSLATIONS.get(lc) or TRANSLATIONS.get(DEFAULT_LANGUAGE, {})
    date_format = '%d/%m/%Y %H:%M:%S' if lc == 'pt' else '%m/%d/%y %I:%M:%S %p'

    info = {
        "software": "FastChecker II",
        "hardware": bundle.get('orientation.pdf_hardware_fallback', 'Leitor RFID'),
        "firmware": "-",
        "serial_number": "-",
        "license": "-",
        "generated_at": datetime.now().strftime(date_format),
    }

    try:
        from .version_config import get_software_info  # type: ignore

        sw_info = get_software_info()
        info["software"] = f"{sw_info.get('name', 'FastChecker II')} v{sw_info.get('version', '')}".strip()
    except Exception:
        pass

    try:
        licenses_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "licenses.json")
        if os.path.exists(licenses_path):
            with open(licenses_path, "r", encoding="utf-8") as fp:
                licenses_content = fp.read()
            info["license"] = bundle.get(
                'orientation.pdf_license_bytes',
                'Registro disponível ({bytes} bytes)',
            ).format(bytes=len(licenses_content))
    except Exception:
        pass

    return info


def _system_info_paragraph(sysinfo: Dict[str, str], styles, translate) -> Paragraph:
    text = (
        f"<b>{translate('pdf.software', 'Software:')}</b> {sysinfo.get('software', '-')}"
        f"<br/><b>{translate('pdf.hardware', 'Hardware:')}</b> {sysinfo.get('hardware', '-')}"
        f"<br/><b>{translate('pdf.firmware', 'Firmware:')}</b> {sysinfo.get('firmware', '-')}"
        f"<br/><b>{translate('pdf.serial_number', 'Serial Number:')}</b> {sysinfo.get('serial_number', '-')}"
        f"<br/><b>{translate('pdf.license', 'Licença:')}</b> {sysinfo.get('license', '-')}"
        f"<br/><b>{translate('pdf.generated_at', 'Gerado em:')}</b> {sysinfo.get('generated_at', '-')}"
    )
    return Paragraph(text, styles['Normal'])


def _prepare_setup_table(setup: Dict, styles, translate) -> Table:
    mode_value = setup.get('mode') or '-'
    if isinstance(mode_value, str):
        mode_key = f"orientation.mode.{mode_value.lower()}"
        mode_label = translate(mode_key, mode_value.capitalize())
    else:
        mode_label = str(mode_value)
    data = [
        [
            Paragraph(f"<b>{translate('orientation.setup.frequency', 'Frequência (MHz)')}</b>", styles['Normal']),
            Paragraph(str(setup.get('frequency_mhz', '-')), styles['Normal'])
        ],
        [
            Paragraph(f"<b>{translate('orientation.setup.start_angle', 'Ângulo Inicial (°)')}</b>", styles['Normal']),
            Paragraph(str(setup.get('start_angle', '-')), styles['Normal'])
        ],
        [
            Paragraph(f"<b>{translate('orientation.setup.end_angle', 'Ângulo Final (°)')}</b>", styles['Normal']),
            Paragraph(str(setup.get('end_angle', '-')), styles['Normal'])
        ],
        [
            Paragraph(f"<b>{translate('orientation.setup.step', 'Step (°)')}</b>", styles['Normal']),
            Paragraph(str(setup.get('step_angle', '-')), styles['Normal'])
        ],
        [
            Paragraph(f"<b>{translate('orientation.setup.selected_epc', 'EPC Selecionado')}</b>", styles['Normal']),
            Paragraph(setup.get('selected_epc') or '-', styles['Normal'])
        ],
        [
            Paragraph(f"<b>{translate('orientation.setup.test_name', 'Nome do Teste')}</b>", styles['Normal']),
            Paragraph(setup.get('test_name') or '-', styles['Normal'])
        ],
    ]
    table = Table(data, colWidths=[170, 295])
    table.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#f1f3f5')),
        ('BOX', (0, 0), (-1, -1), 0.6, colors.HexColor('#adb5bd')),
        ('GRID', (0, 0), (-1, -1), 0.3, colors.HexColor('#ced4da')),
        ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('RIGHTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 4),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
    ]))
    return table


def _build_tests_summary_table(tests: List[Dict], styles, translate) -> Optional[Table]:
    if not tests:
        return None

    header = [
        Paragraph(f"<b>{translate('orientation.table.test_name', 'Nome do Teste')}</b>", styles['Normal']),
        Paragraph(f"<b>{translate('orientation.table.epc', 'EPC')}</b>", styles['Normal']),
        Paragraph(f"<b>{translate('orientation.table.start_angle', 'Ângulo Inicial (°)')}</b>", styles['Normal']),
        Paragraph(f"<b>{translate('orientation.table.end_angle', 'Ângulo Final (°)')}</b>", styles['Normal']),
        Paragraph(f"<b>{translate('orientation.table.step', 'Step (°)')}</b>", styles['Normal']),
        Paragraph(f"<b>{translate('orientation.table.frequency', 'Freq (MHz)')}</b>", styles['Normal']),
        Paragraph(f"<b>{translate('orientation.table.angular_opening', 'Angular Width (°)')}</b>", styles['Normal']),
        Paragraph(f"<b>{translate('orientation.table.main_lobe', 'Main Lobe (°)')}</b>", styles['Normal']),
        Paragraph(f"<b>{translate('orientation.table.timestamp', 'Data/Hora')}</b>", styles['Normal']),
        Paragraph(f"<b>{translate('orientation.table.points', 'Pontos')}</b>", styles['Normal']),
    ]

    rows = [header]
    for test in tests:
        measurements = test.get('measurements') or []
        rows.append([
            Paragraph(test.get('test_name', 'N/A'), styles['Normal']),
            Paragraph(test.get('epc', 'N/A'), styles['Normal']),
            Paragraph(str(test.get('start_angle', '-')), styles['Normal']),
            Paragraph(str(test.get('end_angle', '-')), styles['Normal']),
            Paragraph(str(test.get('step_angle', '-')), styles['Normal']),
            Paragraph(str(test.get('frequency_mhz', '-')), styles['Normal']),
            Paragraph(
                f"{float(angular_opening):.1f}" if (angular_opening := test.get('angular_opening_deg')) is not None else translate('orientation.not_available', '-'),
                styles['Normal']
            ),
            Paragraph(
                f"{float(main_lobe):.1f}" if (main_lobe := test.get('lobular_direction_deg')) is not None else translate('orientation.not_available', '-'),
                styles['Normal']
            ),
            Paragraph(test.get('timestamp', 'N/A'), styles['Normal']),
            Paragraph(str(len(measurements)), styles['Normal']),
        ])

    table = Table(
        rows,
        colWidths=[
            1.2 * inch,
            1.1 * inch,
            0.7 * inch,
            0.7 * inch,
            0.55 * inch,
            0.85 * inch,
            0.85 * inch,
            0.9 * inch,
            0.5 * inch,
        ]
    )
    table.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#e9ecef')),
        ('TEXTCOLOR', (0, 0), (-1, 0), colors.HexColor('#212529')),
        ('ALIGN', (0, 1), (1, -1), 'LEFT'),
        ('ALIGN', (2, 1), (-1, -1), 'CENTER'),
        ('BOX', (0, 0), (-1, -1), 0.6, colors.HexColor('#adb5bd')),
        ('GRID', (0, 0), (-1, -1), 0.3, colors.HexColor('#ced4da')),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f8f9fa')]),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ('LEFTPADDING', (0, 1), (1, -1), 4),
        ('RIGHTPADDING', (0, 1), (1, -1), 6),
    ]))
    return table


def _build_measurements_table(measurements: List[Dict], styles, translate) -> Optional[Table]:
    if not measurements:
        return None

    header = [
        Paragraph(f"<b>{translate('orientation.measurement.angle', 'Ângulo (°)')}</b>", styles['Normal']),
        Paragraph(f"<b>{translate('orientation.measurement.threshold', 'Threshold')}</b>", styles['Normal']),
    ]
    rows = [header]

    for entry in measurements:
        angle_val = entry.get('angle')
        angle = f"{float(angle_val):.1f}" if isinstance(angle_val, (int, float)) else str(angle_val)
        threshold_display = entry.get('threshold_display')
        threshold_raw = entry.get('threshold_raw')
        if threshold_display:
            threshold = threshold_display
        elif threshold_raw is not None:
            threshold = _format_number(threshold_raw, ' dBm')
        else:
            threshold = '-'
        rows.append([
            Paragraph(angle, styles['Normal']),
            Paragraph(threshold, styles['Normal']),
        ])

    table = Table(rows, colWidths=[90, 140])
    table.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#dee2e6')),
        ('TEXTCOLOR', (0, 0), (-1, 0), colors.HexColor('#343a40')),
        ('ALIGN', (0, 0), (-1, 0), 'CENTER'),
        ('ALIGN', (0, 1), (0, -1), 'CENTER'),
        ('ALIGN', (1, 1), (-1, -1), 'LEFT'),
        ('BOX', (0, 0), (-1, -1), 0.6, colors.HexColor('#adb5bd')),
        ('GRID', (0, 0), (-1, -1), 0.3, colors.HexColor('#ced4da')),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ('LEFTPADDING', (1, 1), (-1, -1), 4),
        ('RIGHTPADDING', (1, 1), (-1, -1), 6),
    ]))
    return table


def _build_translation_helpers(language: Optional[str] = None):
    translator = get_translator()
    lang_code = language or translator.get_language()
    dictionary = TRANSLATIONS.get(lang_code) or TRANSLATIONS[DEFAULT_LANGUAGE]

    def translate(key: str, default: str) -> str:
        return dictionary.get(key, default)

    return translator, translate, lang_code


def generate_orientation_report(
    output_pdf_path: str,
    setup: Dict,
    tests: List[Dict],
    session_data: Optional[Dict] = None,
    polar_chart_path: Optional[str] = None,
    system_info: Optional[Dict[str, str]] = None,
    language: Optional[str] = None
) -> str:
    """Gera um relatório PDF seguindo o padrão visual do FastChecker."""
    session_data = session_data or {}
    os.makedirs(os.path.dirname(output_pdf_path) or '.', exist_ok=True)

    doc = SimpleDocTemplate(
        output_pdf_path,
        pagesize=A4,
        leftMargin=54,
        rightMargin=54,
        topMargin=60,
        bottomMargin=42
    )

    styles = getSampleStyleSheet()
    title_style = ParagraphStyle(
        'OrientationTitle',
        parent=styles['Heading1'],
        textColor=colors.HexColor('#2c3e50'),
        fontSize=22,
        alignment=TA_CENTER,
        spaceAfter=18
    )
    heading_style = ParagraphStyle(
        'OrientationHeading',
        parent=styles['Heading2'],
        textColor=colors.HexColor('#2c3e50'),
        fontSize=14,
        spaceBefore=10,
        spaceAfter=10
    )
    normal_style = styles['Normal']
    normal_style.fontSize = 10
    italic_style = ParagraphStyle('Italic', parent=normal_style, fontName='Helvetica-Oblique')

    story: List = []
    _load_logo_if_available(story)

    translator, translate, lang_code = _build_translation_helpers(language)

    story.append(Paragraph(translate('orientation.report_title', 'Relatório - Fast Orientation'), title_style))
    story.append(Spacer(1, 12))

    sysinfo = system_info or _get_system_info(lang_code)
    story.append(Paragraph(translate('pdf.system_info', 'Informações do Sistema'), heading_style))
    story.append(_system_info_paragraph(sysinfo, styles, translate))
    story.append(Spacer(1, 16))

    story.append(Paragraph(translate('pdf.test_configuration', 'Configuração do Teste'), heading_style))
    story.append(_prepare_setup_table(setup, styles, translate))
    story.append(Spacer(1, 16))

    summary_table = _build_tests_summary_table(tests, styles, translate)
    if summary_table:
        story.append(Paragraph(translate('orientation.tests_summary', 'Resumo dos Testes Selecionados'), heading_style))
        story.append(summary_table)
        story.append(Spacer(1, 16))
    else:
        story.append(Paragraph(translate('orientation.no_tests', 'Nenhum teste selecionado no banco de dados.'), italic_style))
        story.append(Spacer(1, 16))

    if polar_chart_path and os.path.exists(polar_chart_path):
        story.append(Paragraph(translate('orientation.polar_chart_title', 'Gráfico Polar Atual'), heading_style))
        story.append(Spacer(1, 6))
        try:
            chart_image = Image(polar_chart_path)
            max_dimension = min(doc.width, 6.5 * inch)
            try:
                original_width = float(chart_image.imageWidth or chart_image.drawWidth)
                original_height = float(chart_image.imageHeight or chart_image.drawHeight)
                if original_width > 0 and original_height > 0:
                    scale = min(max_dimension / original_width, max_dimension / original_height)
                    chart_image.drawWidth = original_width * scale
                    chart_image.drawHeight = original_height * scale
                else:
                    chart_image.drawWidth = max_dimension
                    chart_image.drawHeight = max_dimension
            except Exception:
                chart_image.drawWidth = max_dimension
                chart_image.drawHeight = max_dimension
            story.append(chart_image)
        except Exception as exc:
            story.append(Paragraph(
                translate(
                    'orientation.polar_chart_load_error',
                    'Não foi possível carregar o gráfico polar ({error}).',
                ).format(error=str(exc)),
                italic_style,
            ))
        story.append(Spacer(1, 16))

    for index, test in enumerate(tests):
        if index > 0:
            story.append(cond_page_break_min_remaining_height(5.2))

        story.append(Paragraph(
            translate('orientation.test_details_title', 'Detalhes do Teste: {name}').format(
                name=test.get('test_name', 'N/A')
            ),
            heading_style
        ))

        angular_opening_val = test.get('angular_opening_deg')
        main_lobe_val = test.get('lobular_direction_deg')
        angular_opening_text = (
            f"{float(angular_opening_val):.1f}°" if angular_opening_val is not None else translate('orientation.not_available', '-')
        )
        main_lobe_text = (
            f"{float(main_lobe_val):.1f}°" if main_lobe_val is not None else translate('orientation.not_available', '-')
        )

        details_text = "<br/>".join([
            translate('orientation.detail.epc_line', '<b>EPC:</b> {value}').format(
                value=test.get('epc', 'N/A')
            ),
            translate('orientation.detail.angle_range_line', '<b>Faixa de Ângulo:</b> {start}° a {end}°').format(
                start=test.get('start_angle', '-'),
                end=test.get('end_angle', '-')
            ),
            translate('orientation.detail.step_line', '<b>Step:</b> {value}°').format(
                value=test.get('step_angle', '-')
            ),
            translate('orientation.detail.frequency_line', '<b>Frequência:</b> {value} MHz').format(
                value=test.get('frequency_mhz', '-')
            ),
            translate('orientation.detail.angular_opening_line', '<b>Abertura Angular:</b> {value}').format(
                value=angular_opening_text
            ),
            translate('orientation.detail.main_lobe_line', '<b>Direção Lobular:</b> {value}').format(
                value=main_lobe_text
            ),
            translate('orientation.detail.timestamp_line', '<b>Data/Hora:</b> {value}').format(
                value=test.get('timestamp', 'N/A')
            ),
            translate('orientation.detail.points_line', '<b>Total de medições:</b> {value}').format(
                value=len(test.get('measurements') or [])
            ),
        ])
        story.append(Paragraph(details_text, normal_style))
        story.append(Spacer(1, 8))

        measurements_table = _build_measurements_table(test.get('measurements') or [], styles, translate)
        if measurements_table:
            story.append(measurements_table)
        else:
            story.append(Paragraph(translate('orientation.no_measurements', 'Sem medições registradas para este teste.'), italic_style))
        story.append(Spacer(1, 14))

    live_history = session_data.get('test_history') or []
    if live_history:
        story.append(cond_page_break_min_remaining_height(3.0))
        story.append(Paragraph(translate('orientation.live_section_title', 'Teste Atual / Recente'), heading_style))
        story.append(Paragraph(
            translate('orientation.live_section_description', 'O relatório inclui o histórico registrado durante a sessão atual do módulo.'),
            normal_style
        ))
        story.append(Spacer(1, 8))

        live_table = _build_measurements_table(live_history, styles, translate)
        if live_table:
            story.append(live_table)
        else:
            story.append(Paragraph(translate('orientation.live_section_no_data', 'Sem dados recentes disponíveis.'), italic_style))
        story.append(Spacer(1, 12))

    story.append(cond_page_break_before_footer_block())
    story.append(Paragraph(translate('pdf.additional_info', 'Informações Adicionais'), heading_style))
    story.append(Paragraph(
        translate(
            'pdf.footer_text',
            "Este relatório foi gerado automaticamente pelo FastChecker II.<br/>Sistema de análise e teste RFID profissional."
        ),
        normal_style
    ))
    story.append(Spacer(1, 6))
    timestamp_format = '%d/%m/%Y %H:%M:%S' if lang_code == 'pt' else '%m/%d/%y %I:%M:%S %p'
    story.append(Paragraph(
        f"<b>{translate('pdf.document_generated_at', 'Documento gerado em:')}</b> "
        f"{datetime.now().strftime(timestamp_format)}",
        normal_style
    ))
    story.append(Spacer(1, 12))

    story.append(Paragraph(
        translate('orientation.auto_report_note', 'Relatório automático gerado pelo FastChecker II - Módulo Fast Orientation.'),
        italic_style
    ))

    doc.build(story)
    return output_pdf_path
