import re
import fitz  # PyMuPDF
import logging
from typing import Any, Dict, List, Optional, Tuple

logger = logging.getLogger(__name__)

def make_filename_safe(name: str) -> str:
    """Replace non-alphanumeric characters with underscores and handle German umlauts."""
    umlaut_mapping = str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss", "Ä": "Ae", "Ö": "Oe", "Ü": "Ue"})
    name = name.translate(umlaut_mapping)
    return re.sub(r"[^\w\-]", "_", name).strip("_")

def _safe_text(value: Any, as_integer: bool = False) -> str:
    if value is None or value == "":
        return ""
    if as_integer:
        try:
            return str(int(float(value)))
        except (ValueError, TypeError):
            return str(value).strip()
    return str(value).strip()

def _get_centered_x_between(x1: float, x2: float, text: str, font: str, fontsize: int) -> float:
    font_obj = fitz.Font(fontname=font)
    text_width = font_obj.text_length(str(text), fontsize=fontsize)
    midpoint = (x1 + x2) / 2
    return midpoint - (text_width / 2)

def _get_address_string(street: str, postal_info: str, postcode: str, city: str) -> str:
    if not postal_info:
        return f"{street}, {postcode} {city}"
    return f"{street}, {postal_info}, {postcode} {city}"

def _get_matrices(merged_data: List[Dict[str, Any]]) -> Tuple[List[List[int]], List[List[int]], int, List[str]]:
    errors = []
    age_groups_tn = [(9, "bis 9 Jahre"), (13, "10-13 Jahre"), (17, "14-17 Jahre"), (26, "18-26 Jahre"), (1000, "27+ Jahre")]
    age_groups_gf = [(15, "bis 15 Jahre"), (17, "16-17 Jahre"), (26, "18-26 Jahre"), (44, "27-44 Jahre"), (1000, "45+ Jahre")]

    tn_matrix = [[0, 0, 0] for _ in range(len(age_groups_tn))]
    gf_matrix = [[0, 0, 0] for _ in range(len(age_groups_gf))]

    def get_age_group_idx(age: float, groups: List[Tuple[int, str]]) -> Optional[int]:
        for i, (max_age, _) in enumerate(groups):
            if age <= max_age:
                return i
        return None

    total_sum = 0
    for person in merged_data:
        try:
            age = person.get("age_at_event")
            if age is None:
                errors.append(f"{person.get('name.first_name')} {person.get('name.last_name')}: Alter fehlt.")
                continue
            
            is_leader = person.get("is_leader", False)
            gender = str(person.get("gender", "")).strip().lower()
            if gender not in ["f", "m", "d"]:
                errors.append(f"{person.get('name.first_name')} {person.get('name.last_name')}: Ungültiges Geschlecht '{gender}'.")
                continue
            
            gender_idx = ["f", "m", "d"].index(gender)
            total_sum += 1

            if not is_leader:
                idx = get_age_group_idx(float(age), age_groups_tn)
                if idx is not None:
                    tn_matrix[idx][gender_idx] += 1
            else:
                idx = get_age_group_idx(float(age), age_groups_gf)
                if idx is not None:
                    gf_matrix[idx][gender_idx] += 1
        except Exception as e:
            errors.append(str(e))

    # Add summary rows
    tn_matrix.append([sum(row[i] for row in tn_matrix) for i in range(3)])
    gf_matrix.append([sum(row[i] for row in gf_matrix) for i in range(3)])

    return tn_matrix, gf_matrix, total_sum, errors

def create_info_pdf_bytes(template_path: str, data: List[Dict[str, Any]], event_info: Optional[Dict[str, Any]] = None) -> Tuple[bytes, List[str]]:
    x_ranges = [(144, 164), (202, 222), (260, 280), (412, 432), (470, 490), (522, 552), (305, 350)]
    tn_matrix, gf_matrix, total_sum, errors = _get_matrices(data)

    doc = fitz.open(template_path)
    page = doc[0]

    # Insert TN Stats
    tn_y = 320
    for i, row in enumerate(tn_matrix):
        display_idx = i if i < len(tn_matrix) - 1 else i + 1
        for j, val in enumerate(row):
            x1, x2 = x_ranges[j]
            x = _get_centered_x_between(x1, x2, str(val), "helv", 8)
            page.insert_text((x, tn_y + (50.5 * display_idx)), str(val), fontsize=8)

    # Insert GF Stats
    gf_y = 370.5
    for i, row in enumerate(gf_matrix):
        for j, val in enumerate(row):
            x1, x2 = x_ranges[j + 3]
            x = _get_centered_x_between(x1, x2, str(val), "helv", 8)
            page.insert_text((x, gf_y + (50.5 * i)), str(val), fontsize=8)

    # Total Sum
    x1, x2 = x_ranges[-1]
    x = _get_centered_x_between(x1, x2, str(total_sum), "helv", 8)
    page.insert_text((x, 675), str(total_sum), fontsize=8)

    # Dates
    if event_info:
        try:
            from datetime import datetime
            start = datetime.fromisoformat(event_info["start_date"].replace("Z", "+00:00")).strftime("%d.%m.%Y")
            end = datetime.fromisoformat(event_info["end_date"].replace("Z", "+00:00")).strftime("%d.%m.%Y")
            page.insert_text((130, 140), start, fontsize=10)
            page.insert_text((302, 140), end, fontsize=10)
        except: pass

    pdf_bytes = doc.tobytes()
    doc.close()
    return pdf_bytes, errors

def create_tn_list_pdf_bytes(template_path: str, data: List[Dict[str, Any]], event_info: Optional[Dict[str, Any]] = None) -> bytes:
    first_row_pos = {"name": (92, 200), "adresse": (222, 200), "alter": (442, 200)}
    
    merger = fitz.open()
    chunks = [data[i : i + 10] for i in range(0, len(data), 10)]
    template_doc = fitz.open(template_path)

    title_str = event_info.get("title", "") if event_info else ""
    date_str = ""
    if event_info:
        try:
            from datetime import datetime
            start = datetime.fromisoformat(event_info["start_date"].replace("Z", "+00:00")).strftime("%d.%m.%Y")
            end = datetime.fromisoformat(event_info["end_date"].replace("Z", "+00:00")).strftime("%d.%m.%Y")
            date_str = f"{start} - {end}"
        except: pass

    # Ensure at least one page if data is empty
    if not chunks:
        merger.insert_pdf(template_doc)
        page = merger[-1]
        if title_str:
            page.insert_text((450, 100), title_str, fontsize=12)
        if date_str:
            page.insert_text((60, 100), date_str, fontsize=10)
    else:
        for chunk in chunks:
            merger.insert_pdf(template_doc)
            page = merger[-1]
            
            if title_str:
                page.insert_text((450, 100), title_str, fontsize=12)
            if date_str:
                page.insert_text((60, 100), date_str, fontsize=10)

            for i, row in enumerate(chunk):
                fn = _safe_text(row.get("name.first_name"))
                ln = _safe_text(row.get("name.last_name"))
                street = _safe_text(row.get("address.street"))
                info = _safe_text(row.get("address.postal_info"))
                city = _safe_text(row.get("address.city"))
                pc = _safe_text(row.get("address.postcode"), as_integer=True)
                age = _safe_text(row.get("age_at_event"), as_integer=True)

                if fn and ln:
                    page.insert_text((first_row_pos["name"][0], first_row_pos["name"][1] + 28.5 * i), f"{ln}, {fn}", fontsize=8)
                if street and pc and city:
                    addr = _get_address_string(street, info, pc, city)
                    page.insert_text((first_row_pos["adresse"][0], first_row_pos["adresse"][1] + 28.5 * i), addr, fontsize=8)
                if age:
                    page.insert_text((first_row_pos["alter"][0], first_row_pos["alter"][1] + 28.5 * i), age, fontsize=8)

    template_doc.close()
    pdf_bytes = merger.tobytes()
    merger.close()
    return pdf_bytes

async def generate_event_pdfs(
    lst_id: str,
    group_config=None,
) -> Tuple[Optional[bytes], Optional[bytes], Optional[Dict[str, Any]]]:
    from src.services.api_service import fetch_event_tn_data
    import os
    
    tn, gf, event_info = await fetch_event_tn_data(lst_id, group_config=group_config)
    if not event_info:
        return None, None, None

    # Merge TN and GF info
    leader_map = {(l["name.first_name"], l["name.last_name"]) for l in gf}
    for person in tn:
        if (person["name.first_name"], person["name.last_name"]) in leader_map:
            person["is_leader"] = True
    
    # Sort by last name
    tn.sort(key=lambda x: str(x.get("name.last_name", "")).lower())

    # Paths to templates
    info_tpl = os.path.join("src", "assets", "info-zu-den-teilnehmenden.pdf")
    list_tpl = os.path.join("src", "assets", "TN-Liste-2.pdf")

    # Ensure assets exist (mock copy for now, or assume they are there)
    if not os.path.exists(info_tpl) or not os.path.exists(list_tpl):
        logger.error("PDF templates not found in src/assets")
        return None, None, None

    info_bytes, _ = create_info_pdf_bytes(info_tpl, tn, event_info)
    list_bytes = create_tn_list_pdf_bytes(list_tpl, tn, event_info)

    return list_bytes, info_bytes, event_info
