import logging
import json
from datetime import date, datetime
from typing import Optional
from fastapi import APIRouter, HTTPException, Query, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
from app.core.database import execute_query

logger = logging.getLogger(__name__)
router = APIRouter()


def _render_api_print_bridge(api_path: str, page_title: str) -> str:
        safe_api_path = json.dumps(api_path)
        safe_title = json.dumps(page_title)
        return f"""
<!doctype html>
<html lang=\"da\">
<head>
    <meta charset=\"utf-8\" />
    <title>{page_title}</title>
    <style>
        body {{ font-family: 'Segoe UI', sans-serif; margin: 20px; color: #0f172a; }}
        .muted {{ color: #475569; }}
        .error {{ color: #b42318; }}
    </style>
</head>
<body>
    <div id=\"state\" class=\"muted\">Henter printvisning...</div>
    <script>
        (async function () {{
            const apiPath = {safe_api_path};
            const pageTitle = {safe_title};
            const token = localStorage.getItem('access_token') || sessionStorage.getItem('access_token');
            const headers = token ? {{ Authorization: `Bearer ${{token}}` }} : {{}};

            try {{
                const res = await fetch(apiPath, {{ method: 'GET', headers, credentials: 'include' }});
                if (!res.ok) {{
                    let detail = `Kunne ikke hente printvisning (${{res.status}})`;
                    try {{
                        const payload = await res.json();
                        if (payload && payload.detail) detail = payload.detail;
                    }} catch (_) {{}}
                    document.getElementById('state').className = 'error';
                    document.getElementById('state').textContent = detail;
                    return;
                }}

                const html = await res.text();
                document.open();
                document.write(html);
                document.close();
                if (!document.title) document.title = pageTitle;
            }} catch (error) {{
                document.getElementById('state').className = 'error';
                document.getElementById('state').textContent = `Fejl ved hentning af printvisning: ${{error?.message || 'Ukendt fejl'}}`;
            }}
        }})();
    </script>
</body>
</html>
"""


def _is_deadline_overdue(deadline_value) -> bool:
    if not deadline_value:
        return False
    if isinstance(deadline_value, datetime):
        return deadline_value.date() < date.today()
    if isinstance(deadline_value, date):
        return deadline_value < date.today()
    return False

# Setup template directory
templates = Jinja2Templates(directory="app")


def _fetch_assignment_users():
    return execute_query(
        """
        SELECT user_id, COALESCE(full_name, username) AS display_name
        FROM users
        ORDER BY display_name
        """,
        ()
    ) or []


def _fetch_assignment_groups():
    try:
        return execute_query(
            """
            SELECT id, name
            FROM groups
            ORDER BY name
            """,
            ()
        ) or []
    except Exception as e:
        logger.warning("⚠️ Could not load assignment groups (groups table missing?): %s", e)
        return []


def _coerce_optional_int(value: Optional[str]) -> Optional[int]:
    """Convert empty strings and None to None, otherwise parse as int."""
    if value is None or value == "":
        return None
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


def _fetch_case_status_options() -> list[str]:
    default_statuses = ["åben", "under behandling", "afventer", "løst", "lukket"]
    values = []
    seen = set()

    def _add(value: Optional[str]) -> None:
        candidate = str(value or "").strip()
        if not candidate:
            return
        key = candidate.lower()
        if key in seen:
            return
        seen.add(key)
        values.append(candidate)

    setting_row = execute_query(
        "SELECT value FROM settings WHERE key = %s",
        ("case_statuses",)
    )

    if setting_row and setting_row[0].get("value"):
        try:
            parsed = json.loads(setting_row[0].get("value") or "[]")
            for item in parsed if isinstance(parsed, list) else []:
                value = ""
                if isinstance(item, str):
                    value = item.strip()
                elif isinstance(item, dict):
                    value = str(item.get("value") or "").strip()
                _add(value)
        except Exception:
            pass

    statuses = execute_query("SELECT DISTINCT status FROM sag_sager WHERE deleted_at IS NULL ORDER BY status", ()) or []

    for row in statuses:
        _add(row.get("status"))

    for default in default_statuses:
        _add(default)

    return values


def _fetch_closed_case_statuses() -> list[str]:
    values = []
    seen = set()

    def _add(value: Optional[str]) -> None:
        candidate = str(value or "").strip().lower()
        if not candidate or candidate in seen:
            return
        seen.add(candidate)
        values.append(candidate)

    setting_row = execute_query(
        "SELECT value FROM settings WHERE key = %s",
        ("case_statuses",)
    )

    if setting_row and setting_row[0].get("value"):
        try:
            parsed = json.loads(setting_row[0].get("value") or "[]")
            for item in parsed if isinstance(parsed, list) else []:
                if isinstance(item, dict) and item.get("is_closed"):
                    _add(item.get("value"))
        except Exception:
            pass

    if not values:
        for fallback in ["lukket", "løst", "afsluttet", "closed", "resolved", "done"]:
            _add(fallback)

    return values


@router.get("/sag", response_class=HTMLResponse)
async def sager_liste(
    request: Request,
    status: str = Query(None),
    tag: str = Query(None),
    customer_id: str = Query(None),
    ansvarlig_bruger_id: str = Query(None),
    assigned_group_id: str = Query(None),
    unassigned: bool = Query(False),
    include_deferred: bool = Query(False),
):
    """Display list of all cases."""
    try:
        closed_statuses = _fetch_closed_case_statuses()
        # Coerce string params to optional ints
        customer_id_int = _coerce_optional_int(customer_id)
        requested_unassigned = bool(unassigned) or str(ansvarlig_bruger_id or "").strip().upper() == "__UNASSIGNED__"
        ansvarlig_bruger_id_int = None if requested_unassigned else _coerce_optional_int(ansvarlig_bruger_id)
        assigned_group_id_int = _coerce_optional_int(assigned_group_id)
        query = """
            SELECT s.*, 
                   c.name as customer_name,
                   CONCAT(COALESCE(cont.first_name, ''), ' ', COALESCE(cont.last_name, '')) as kontakt_navn,
                   COALESCE(u.full_name, u.username) AS ansvarlig_navn,
                 g.name AS assigned_group_name,
                 nt.title AS next_todo_title,
                      nt.due_date AS next_todo_due_date,
                      COALESCE(ec.unread_email_count, 0) AS unread_email_count,
                      ec.oldest_unread_received_date,
                      CASE
                          WHEN COALESCE(ec.unread_email_count, 0) = 0 THEN 'none'
                          WHEN ec.oldest_unread_received_date <= NOW() - INTERVAL '72 hours' THEN 'hot'
                          WHEN ec.oldest_unread_received_date <= NOW() - INTERVAL '24 hours' THEN 'warm'
                          ELSE 'fresh'
                      END AS unread_email_level
            FROM sag_sager s
            LEFT JOIN customers c ON s.customer_id = c.id
            LEFT JOIN users u ON u.user_id = s.ansvarlig_bruger_id
            LEFT JOIN groups g ON g.id = s.assigned_group_id
            LEFT JOIN LATERAL (
                SELECT cc.contact_id 
                FROM contact_companies cc
                WHERE cc.customer_id = c.id
                ORDER BY cc.is_primary DESC NULLS LAST, cc.id ASC
                LIMIT 1
            ) cc_first ON true
            LEFT JOIN contacts cont ON cc_first.contact_id = cont.id
                        LEFT JOIN LATERAL (
                                SELECT t.title, t.due_date
                                FROM sag_todo_steps t
                                WHERE t.sag_id = s.id
                                    AND t.deleted_at IS NULL
                                    AND t.is_done = FALSE
                            ORDER BY
                                CASE
                                    WHEN t.is_next THEN 0
                                    WHEN t.due_date IS NOT NULL THEN 1
                                    ELSE 2
                                END,
                                t.due_date ASC NULLS LAST,
                                t.created_at ASC
                                LIMIT 1
                        ) nt ON true
            LEFT JOIN LATERAL (
                SELECT
                    COUNT(*) FILTER (WHERE em.deleted_at IS NULL AND COALESCE(em.is_read, FALSE) = FALSE) AS unread_email_count,
                    MIN(em.received_date) FILTER (WHERE em.deleted_at IS NULL AND COALESCE(em.is_read, FALSE) = FALSE) AS oldest_unread_received_date
                FROM sag_emails se
                JOIN email_messages em ON em.id = se.email_id
                WHERE se.sag_id = s.id
            ) ec ON true
            LEFT JOIN sag_sager ds ON ds.id = s.deferred_until_case_id
            WHERE s.deleted_at IS NULL
        """
        params = []

        if not include_deferred:
            query += " AND ("
            query += "s.deferred_until IS NULL"
            query += " OR s.deferred_until <= NOW()"
            query += " OR (s.deferred_until_case_id IS NOT NULL AND s.deferred_until_status IS NOT NULL AND ds.status = s.deferred_until_status)"
            query += ")"
            query += " AND (s.start_date IS NULL OR s.start_date <= NOW())"
        
        normalized_status = str(status or "").strip().lower()
        if normalized_status == "all":
            pass
        elif normalized_status:
            query += " AND s.status = %s"
            params.append(status)
        else:
            placeholders = ", ".join(["%s"] * len(closed_statuses))
            query += f" AND LOWER(COALESCE(s.status, '')) NOT IN ({placeholders})"
            params.extend(closed_statuses)
        if customer_id_int:
            query += " AND s.customer_id = %s"
            params.append(customer_id_int)
        if requested_unassigned:
            query += " AND s.ansvarlig_bruger_id IS NULL"
        elif ansvarlig_bruger_id_int:
            query += " AND s.ansvarlig_bruger_id = %s"
            params.append(ansvarlig_bruger_id_int)
        if assigned_group_id_int:
            query += " AND s.assigned_group_id = %s"
            params.append(assigned_group_id_int)
        
        query += " ORDER BY s.created_at DESC"

        try:
            sager = execute_query(query, tuple(params))
        except Exception as e:
            logger.warning("⚠️ Advanced SAG list query failed, using compatibility fallback: %s", e)

            fallback_query = """
                SELECT s.*, 
                       c.name as customer_name,
                       '' as kontakt_navn,
                       COALESCE(u.full_name, u.username) AS ansvarlig_navn,
                       NULL::text AS assigned_group_name,
                       NULL::text AS next_todo_title,
                       NULL::timestamp AS next_todo_due_date,
                       COALESCE(ec.unread_email_count, 0) AS unread_email_count,
                       ec.oldest_unread_received_date,
                       CASE
                          WHEN COALESCE(ec.unread_email_count, 0) = 0 THEN 'none'
                          WHEN ec.oldest_unread_received_date <= NOW() - INTERVAL '72 hours' THEN 'hot'
                          WHEN ec.oldest_unread_received_date <= NOW() - INTERVAL '24 hours' THEN 'warm'
                          ELSE 'fresh'
                       END AS unread_email_level
                FROM sag_sager s
                LEFT JOIN customers c ON s.customer_id = c.id
                LEFT JOIN users u ON u.user_id = s.ansvarlig_bruger_id
                LEFT JOIN LATERAL (
                    SELECT
                        COUNT(*) FILTER (WHERE em.deleted_at IS NULL AND COALESCE(em.is_read, FALSE) = FALSE) AS unread_email_count,
                        MIN(em.received_date) FILTER (WHERE em.deleted_at IS NULL AND COALESCE(em.is_read, FALSE) = FALSE) AS oldest_unread_received_date
                    FROM sag_emails se
                    JOIN email_messages em ON em.id = se.email_id
                    WHERE se.sag_id = s.id
                ) ec ON true
                WHERE s.deleted_at IS NULL
            """
            fallback_params = []

            if not include_deferred:
                fallback_query += " AND (s.deferred_until IS NULL OR s.deferred_until <= NOW())"
                fallback_query += " AND (s.start_date IS NULL OR s.start_date <= NOW())"

            if normalized_status == "all":
                pass
            elif normalized_status:
                fallback_query += " AND s.status = %s"
                fallback_params.append(status)
            else:
                placeholders = ", ".join(["%s"] * len(closed_statuses))
                fallback_query += f" AND LOWER(COALESCE(s.status, '')) NOT IN ({placeholders})"
                fallback_params.extend(closed_statuses)
            if customer_id_int:
                fallback_query += " AND s.customer_id = %s"
                fallback_params.append(customer_id_int)
            if requested_unassigned:
                fallback_query += " AND s.ansvarlig_bruger_id IS NULL"
            elif ansvarlig_bruger_id_int:
                fallback_query += " AND s.ansvarlig_bruger_id = %s"
                fallback_params.append(ansvarlig_bruger_id_int)

            fallback_query += " ORDER BY s.created_at DESC"
            sager = execute_query(fallback_query, tuple(fallback_params)) or []
        
        # Fetch relations for all cases
        relations_query = """
            SELECT 
                sr.kilde_sag_id,
                sr.målsag_id,
                sr.relationstype,
                sr.id as relation_id
            FROM sag_relationer sr
            WHERE sr.deleted_at IS NULL
        """
        try:
            all_relations = execute_query(relations_query, ())
        except Exception as e:
            logger.warning("⚠️ Could not load case relations: %s", e)
            all_relations = []
        child_ids = set()
        
        # Build relations map: {sag_id: [list of related sag_ids]}
        relations_map = {}
        for rel in all_relations or []:
            if rel.get('målsag_id') is not None:
                child_ids.add(rel['målsag_id'])
            # Add forward relation
            if rel['kilde_sag_id'] not in relations_map:
                relations_map[rel['kilde_sag_id']] = []
            relations_map[rel['kilde_sag_id']].append({
                'target_id': rel['målsag_id'],
                'type': rel['relationstype'],
                'direction': 'forward'
            })
            # Add backward relation
            if rel['målsag_id'] not in relations_map:
                relations_map[rel['målsag_id']] = []
            relations_map[rel['målsag_id']].append({
                'target_id': rel['kilde_sag_id'],
                'type': rel['relationstype'],
                'direction': 'backward'
            })
        
        # Filter by tag if provided
        if tag and sager:
            sag_ids = [s['id'] for s in sager]
            tag_query = "SELECT sag_id FROM sag_tags WHERE tag_navn = %s AND deleted_at IS NULL"
            tagged = execute_query(tag_query, (tag,))
            tagged_ids = set(t['sag_id'] for t in tagged)
            sager = [s for s in sager if s['id'] in tagged_ids]
        
        # Fetch all distinct statuses and tags for filters
        status_options = _fetch_case_status_options()

        current_status = str(status or "").strip()
        if current_status and current_status.lower() not in {s.lower() for s in status_options}:
            status_options.append(current_status)
        all_tags = execute_query("SELECT DISTINCT tag_navn FROM sag_tags WHERE deleted_at IS NULL ORDER BY tag_navn", ())

        toggle_include_deferred_url = str(
            request.url.include_query_params(include_deferred="0" if include_deferred else "1")
        )
        
        return templates.TemplateResponse("modules/sag/templates/index.html", {
            "request": request,
            "sager": sager,
            "relations_map": relations_map,
            "child_ids": list(child_ids),
            "statuses": status_options,
            "all_tags": [t['tag_navn'] for t in all_tags],
            "current_status": status,
            "current_tag": tag,
            "include_deferred": include_deferred,
            "toggle_include_deferred_url": toggle_include_deferred_url,
            "assignment_users": _fetch_assignment_users(),
            "assignment_groups": _fetch_assignment_groups(),
            "current_customer_id": customer_id_int,
            "current_ansvarlig_bruger_id": ansvarlig_bruger_id_int,
            "current_assigned_group_id": assigned_group_id_int,
            "current_unassigned": requested_unassigned,
            "closed_statuses": closed_statuses,
        })
    except Exception:
        logger.exception("❌ Error displaying case list")
        return templates.TemplateResponse("modules/sag/templates/index.html", {
            "request": request,
            "sager": [],
            "relations_map": {},
            "child_ids": [],
            "statuses": _fetch_case_status_options(),
            "all_tags": [],
            "current_status": status,
            "current_tag": tag,
            "include_deferred": include_deferred,
            "toggle_include_deferred_url": str(request.url),
            "assignment_users": [],
            "assignment_groups": [],
            "current_customer_id": customer_id_int,
            "current_ansvarlig_bruger_id": ansvarlig_bruger_id_int,
            "current_assigned_group_id": assigned_group_id_int,
            "current_unassigned": requested_unassigned,
            "closed_statuses": _fetch_closed_case_statuses(),
        })

@router.get("/sag/new", response_class=HTMLResponse)
async def opret_sag_side(request: Request):
    """Show create case form."""
    return templates.TemplateResponse("modules/sag/templates/create.html", {
        "request": request,
        "assignment_users": _fetch_assignment_users(),
        "assignment_groups": _fetch_assignment_groups(),
    })


@router.get("/sag/{sag_id}/work-orders/print", response_class=HTMLResponse)
async def sag_work_order_print_page(request: Request, sag_id: int):
    auto_print = str(request.query_params.get("auto_print", "0")).lower() in {"1", "true", "yes", "on"}
    api_path = f"/api/v1/sag/{sag_id}/work-orders/print"
    if auto_print:
        api_path = f"{api_path}?auto_print=1"
    html = _render_api_print_bridge(
        api_path=api_path,
        page_title=f"Arbejdsseddel SAG-{sag_id}",
    )
    return HTMLResponse(content=html)


@router.get("/sag/{sag_id}/labels/hardware/print", response_class=HTMLResponse)
async def sag_hardware_labels_print_page(request: Request, sag_id: int):
    auto_print = str(request.query_params.get("auto_print", "0")).lower() in {"1", "true", "yes", "on"}
    api_path = f"/api/v1/sag/{sag_id}/labels/hardware/print"
    if auto_print:
        api_path = f"{api_path}?auto_print=1"
    html = _render_api_print_bridge(
        api_path=api_path,
        page_title=f"Hardware labels SAG-{sag_id}",
    )
    return HTMLResponse(content=html)

@router.get("/sag/varekob-salg", response_class=HTMLResponse)
async def sag_varekob_salg(request: Request):
    """Display orders overview for all purchases and sales."""
    return templates.TemplateResponse("modules/sag/templates/varekob_salg.html", {
        "request": request,
    })

@router.get("/sag/{sag_id}", response_class=HTMLResponse)
async def sag_detaljer(request: Request, sag_id: int):
    """Redirect legacy case details URL to v3."""
    return RedirectResponse(url=f"/sag/{sag_id}/v3", status_code=307)

    """Display case details."""
    try:
        # Fetch main case
        sag_query = """
            SELECT s.*,
                   COALESCE(u.full_name, u.username) AS ansvarlig_navn,
                   g.name AS assigned_group_name
            FROM sag_sager s
            LEFT JOIN users u ON u.user_id = s.ansvarlig_bruger_id
            LEFT JOIN groups g ON g.id = s.assigned_group_id
            WHERE s.id = %s AND s.deleted_at IS NULL
        """
        sag_result = execute_query(sag_query, (sag_id,))
        
        if not sag_result:
            raise HTTPException(status_code=404, detail="Case not found")
        
        sag = sag_result[0]
        
        # Fetch tags (Support both Legacy sag_tags and New entity_tags)
        # First try the new system (entity_tags) which the valid frontend uses
        tags_query = """
            SELECT t.name as tag_navn 
            FROM tags t
            JOIN entity_tags et ON t.id = et.tag_id
            WHERE et.entity_type = 'case' AND et.entity_id = %s
        """
        tags = execute_query(tags_query, (sag_id,))

        # If empty, try legacy table fallback
        if not tags:
            tags_query_legacy = "SELECT * FROM sag_tags WHERE sag_id = %s AND deleted_at IS NULL ORDER BY created_at DESC"
            tags = execute_query(tags_query_legacy, (sag_id,))

        buzzwords = []
        try:
            buzzwords = execute_query(
                """
                SELECT
                    sb.id AS relation_id,
                    sb.sag_id,
                    b.id AS buzzword_id,
                    b.word,
                    sb.created_at
                FROM sag_buzzwords sb
                JOIN buzzwords b ON b.id = sb.buzzword_id
                WHERE sb.sag_id = %s
                  AND sb.deleted_at IS NULL
                  AND b.deleted_at IS NULL
                ORDER BY b.word ASC
                """,
                (sag_id,),
            ) or []
        except Exception as e:
            logger.warning("⚠️ Could not load buzzwords for case %s: %s", sag_id, e)
            buzzwords = []

        buzzwords = []
        try:
            buzzwords = execute_query(
                """
                SELECT
                    sb.id AS relation_id,
                    sb.sag_id,
                    b.id AS buzzword_id,
                    b.word,
                    sb.created_at
                FROM sag_buzzwords sb
                JOIN buzzwords b ON b.id = sb.buzzword_id
                WHERE sb.sag_id = %s
                  AND sb.deleted_at IS NULL
                  AND b.deleted_at IS NULL
                ORDER BY b.word ASC
                """,
                (sag_id,),
            ) or []
        except Exception as e:
            logger.warning("⚠️ Could not load buzzwords for case %s: %s", sag_id, e)
            buzzwords = []
        
        # Fetch relations
        relationer_query = """
            SELECT sr.*, 
                   ss_kilde.titel as kilde_titel,
                   ss_mål.titel as mål_titel
            FROM sag_relationer sr
            JOIN sag_sager ss_kilde ON sr.kilde_sag_id = ss_kilde.id
            JOIN sag_sager ss_mål ON sr.målsag_id = ss_mål.id
            WHERE (sr.kilde_sag_id = %s OR sr.målsag_id = %s)
            AND sr.deleted_at IS NULL
            ORDER BY sr.created_at DESC
        """
        relationer = execute_query(relationer_query, (sag_id, sag_id))
        
        # --- Relation Tree Construction ---
        relation_tree = []
        try:
            from app.modules.sag.services.relation_service import RelationService
            relation_tree = RelationService.get_relation_tree(sag_id)
        except Exception as e:
            logger.error(f"Error building relation tree: {e}")
            relation_tree = []

        # Fallback: if tree builder fails/returns empty but relations exist, render a minimal flat tree.
        if not relation_tree and relationer:
            try:
                root_node = {
                    "case": {
                        "id": sag.get("id"),
                        "titel": sag.get("titel"),
                        "status": sag.get("status"),
                        "type": sag.get("type"),
                        "template_key": sag.get("template_key"),
                    },
                    "relation_type": None,
                    "relation_id": None,
                    "is_current": True,
                    "children": [],
                }

                seen_related = set()
                for rel in relationer or []:
                    source_id = rel.get("kilde_sag_id")
                    target_id = rel.get("målsag_id")
                    related_id = target_id if source_id == sag_id else source_id
                    if not related_id or related_id in seen_related:
                        continue
                    seen_related.add(related_id)

                    root_node["children"].append({
                        "case": {
                            "id": related_id,
                            "titel": rel.get("mål_titel") if source_id == sag_id else rel.get("kilde_titel"),
                            "status": None,
                            "type": None,
                            "template_key": None,
                        },
                        "relation_type": rel.get("relationstype") or "Relateret til",
                        "relation_id": rel.get("id"),
                        "is_current": False,
                        "children": [],
                    })

                relation_tree = [root_node]
            except Exception as fallback_err:
                logger.warning("⚠️ Could not build fallback relation tree: %s", fallback_err)
        
        # Fetch customer info if customer_id exists
        customer = None
        hovedkontakt = None
        if sag.get('customer_id'):
            customer_query = "SELECT * FROM customers WHERE id = %s"
            customer_result = execute_query(customer_query, (sag['customer_id'],))
            if customer_result:
                customer = customer_result[0]

        # Fetch hovedkontakt (primary contact) for case via sag_kontakter
        kontakt_query = """
            SELECT c.*
            FROM contacts c
            JOIN sag_kontakter sk ON c.id = sk.contact_id
            WHERE sk.sag_id = %s AND sk.deleted_at IS NULL AND sk.is_primary = TRUE
            LIMIT 1
        """
        kontakt_result = execute_query(kontakt_query, (sag_id,))
        if kontakt_result:
            hovedkontakt = kontakt_result[0]
        else:
            fallback_query = """
                SELECT c.*
                FROM contacts c
                JOIN sag_kontakter sk ON c.id = sk.contact_id
                WHERE sk.sag_id = %s AND sk.deleted_at IS NULL
                ORDER BY sk.created_at ASC
                LIMIT 1
            """
            fallback_result = execute_query(fallback_query, (sag_id,))
            if fallback_result:
                hovedkontakt = fallback_result[0]

        # Fetch prepaid cards for customer
        # Cast remaining_hours to float to avoid Jinja formatting issues with Decimal
        # DEBUG: Logging customer ID
        prepaid_cards = []
        if sag.get('customer_id'):
            cid = sag.get('customer_id')
            logger.info(f"🔎 Looking up prepaid cards for Sag {sag_id}, Customer ID: {cid} (Type: {type(cid)})")
            
            pc_query = """
                SELECT id, card_number, CAST(remaining_hours AS FLOAT) as remaining_hours, expires_at
                FROM tticket_prepaid_cards
                WHERE customer_id = %s
                  AND status = 'active'
                  AND remaining_hours > 0
                ORDER BY created_at DESC
            """
            prepaid_cards = execute_query(pc_query, (cid,))
            logger.info(f"💳 Found {len(prepaid_cards)} prepaid cards for customer {cid}")

        # Fetch fixed-price agreements for customer
        fixed_price_agreements = []
        if sag.get('customer_id'):
            cid = sag.get('customer_id')
            logger.info(f"🔎 Looking up fixed-price agreements for Sag {sag_id}, Customer ID: {cid}")
            
            fpa_query = """
                SELECT 
                    a.id, 
                    a.agreement_number, 
                    a.monthly_hours,
                    COALESCE(bp.remaining_hours, a.monthly_hours) as remaining_hours_this_month
                FROM customer_fixed_price_agreements a
                LEFT JOIN fixed_price_billing_periods bp ON (
                    a.id = bp.agreement_id 
                    AND bp.period_start <= CURRENT_DATE 
                    AND bp.period_end >= CURRENT_DATE
                )
                WHERE a.customer_id = %s
                  AND a.status = 'active'
                  AND (a.end_date IS NULL OR a.end_date >= CURRENT_DATE)
                ORDER BY a.created_at DESC
            """
            fixed_price_agreements = execute_query(fpa_query, (cid,))
            logger.info(f"📋 Found {len(fixed_price_agreements)} fixed-price agreements for customer {cid}")

        # Fetch Nextcloud Instance for this customer
        nextcloud_instance = None
        if customer:
            nc_query = "SELECT * FROM nextcloud_instances WHERE customer_id = %s AND deleted_at IS NULL"
            nc_result = execute_query(nc_query, (customer['id'],))
            if nc_result:
                nextcloud_instance = nc_result[0]

        # Fetch linked contacts
        contacts_query = """
            SELECT
                sk.*,
                c.first_name || ' ' || c.last_name as contact_name,
                c.email as contact_email,
                c.phone,
                c.mobile,
                c.title,
                company.customer_name
            FROM sag_kontakter sk
            JOIN contacts c ON sk.contact_id = c.id
            LEFT JOIN LATERAL (
                SELECT cu.name AS customer_name
                FROM contact_companies cc
                JOIN customers cu ON cu.id = cc.customer_id
                WHERE cc.contact_id = c.id
                ORDER BY cc.is_primary DESC, cu.name
                LIMIT 1
            ) company ON TRUE
            WHERE sk.sag_id = %s AND sk.deleted_at IS NULL
        """
        contacts = execute_query(contacts_query, (sag_id,))

        # Fetch linked customers
        customers_query = """
            SELECT sk.*, c.name as customer_name, c.email as customer_email
            FROM sag_kunder sk
            JOIN customers c ON sk.customer_id = c.id
            WHERE sk.sag_id = %s AND sk.deleted_at IS NULL
        """
        customers = execute_query(customers_query, (sag_id,))
        
        # Fetch comments
        comments_query = "SELECT * FROM sag_kommentarer WHERE sag_id = %s AND deleted_at IS NULL ORDER BY created_at DESC"
        comments = execute_query(comments_query, (sag_id,))

        # Fetch Solution
        solution_query = "SELECT * FROM sag_solutions WHERE sag_id = %s"
        solution_res = execute_query(solution_query, (sag_id,))
        solution = solution_res[0] if solution_res else None

        # Fetch Time Entries
        time_query = "SELECT * FROM tmodule_times WHERE sag_id = %s ORDER BY worked_date DESC"
        time_entries = execute_query(time_query, (sag_id,))

        # Fetch linked telephony call history
        call_history_query = """
            SELECT
                t.id,
                t.callid,
                t.direction,
                t.ekstern_nummer,
                t.started_at,
                t.ended_at,
                t.duration_sec,
                u.username,
                u.full_name,
                CONCAT(COALESCE(c.first_name, ''), ' ', COALESCE(c.last_name, '')) AS contact_name
            FROM telefoni_opkald t
            LEFT JOIN users u ON u.user_id = t.bruger_id
            LEFT JOIN contacts c ON c.id = t.kontakt_id
            WHERE t.sag_id = %s
            ORDER BY t.started_at DESC
            LIMIT 200
        """
        call_history = execute_query(call_history_query, (sag_id,))

        # Check for nextcloud integration (case-insensitive, insensitive to whitespace)
        logger.info(f"Checking tags for Nextcloud on case {sag_id}: {tags}")
        is_nextcloud = any(t['tag_navn'] and t['tag_navn'].strip().lower() == 'nextcloud' for t in tags)
        logger.info(f"is_nextcloud result: {is_nextcloud}")

        related_case_options = []
        try:
            related_ids = set()
            for rel in relationer or []:
                related_ids.add(rel["kilde_sag_id"])
                related_ids.add(rel["målsag_id"])
            related_ids.discard(sag_id)
            if related_ids:
                placeholders = ",".join(["%s"] * len(related_ids))
                related_query = f"SELECT id, titel, status FROM sag_sager WHERE id IN ({placeholders}) AND deleted_at IS NULL"
                related_case_options = execute_query(related_query, tuple(related_ids))
        except Exception as e:
            logger.error("❌ Error building related case options: %s", e)
            related_case_options = []

        pipeline_stages = []
        try:
            pipeline_stages = execute_query(
                "SELECT id, name, color, sort_order FROM pipeline_stages ORDER BY sort_order ASC, id ASC",
                (),
            )
        except Exception as e:
            logger.warning("⚠️ Could not load pipeline stages: %s", e)
            pipeline_stages = []

        status_options = _fetch_case_status_options()
        current_status = str(sag.get("status") or "").strip()
        if current_status and current_status.lower() not in {s.lower() for s in status_options}:
            status_options.append(current_status)
        is_deadline_overdue = _is_deadline_overdue(sag.get("deadline"))

        return templates.TemplateResponse("modules/sag/templates/detail.html", {
            "request": request,
            "case": sag,
            "customer": customer,
            "hovedkontakt": hovedkontakt,
            "contacts": contacts,
            "customers": customers,
            "prepaid_cards": prepaid_cards,
            "fixed_price_agreements": fixed_price_agreements,
            "tags": tags,
            "buzzwords": buzzwords,
            "buzzwords": buzzwords,

            "relationer": relationer,
            "relation_tree": relation_tree,
            "comments": comments,
            "solution": solution,
            "time_entries": time_entries,
            "call_history": call_history,
            "is_nextcloud": is_nextcloud,
            "nextcloud_instance": nextcloud_instance,
            "related_case_options": related_case_options,
            "pipeline_stages": pipeline_stages,
            "status_options": status_options,
            "is_deadline_overdue": is_deadline_overdue,
            "assignment_users": _fetch_assignment_users(),
            "assignment_groups": _fetch_assignment_groups(),
        })
    except HTTPException:
        raise
    except Exception as e:
        logger.error("❌ Error displaying case details: %s", e)
        raise HTTPException(status_code=500, detail="Failed to load case details")


@router.get("/sag/{sag_id}/v3", response_class=HTMLResponse)
async def sag_detaljer_v3(request: Request, sag_id: int):
    """Display case details."""
    try:
        # Fetch main case
        sag_query = """
            SELECT s.*,
                   COALESCE(u.full_name, u.username) AS ansvarlig_navn,
                   g.name AS assigned_group_name
            FROM sag_sager s
            LEFT JOIN users u ON u.user_id = s.ansvarlig_bruger_id
            LEFT JOIN groups g ON g.id = s.assigned_group_id
            WHERE s.id = %s AND s.deleted_at IS NULL
        """
        sag_result = execute_query(sag_query, (sag_id,))
        
        if not sag_result:
            raise HTTPException(status_code=404, detail="Case not found")
        
        sag = sag_result[0]
        
        # Fetch tags (Support both Legacy sag_tags and New entity_tags)
        # First try the new system (entity_tags) which the valid frontend uses
        tags_query = """
            SELECT t.name as tag_navn 
            FROM tags t
            JOIN entity_tags et ON t.id = et.tag_id
            WHERE et.entity_type = 'case' AND et.entity_id = %s
        """
        tags = execute_query(tags_query, (sag_id,))

        # If empty, try legacy table fallback
        if not tags:
            tags_query_legacy = "SELECT * FROM sag_tags WHERE sag_id = %s AND deleted_at IS NULL ORDER BY created_at DESC"
            tags = execute_query(tags_query_legacy, (sag_id,))
        
        # Fetch relations
        relationer_query = """
            SELECT sr.*, 
                   ss_kilde.titel as kilde_titel,
                   ss_mål.titel as mål_titel
            FROM sag_relationer sr
            JOIN sag_sager ss_kilde ON sr.kilde_sag_id = ss_kilde.id
            JOIN sag_sager ss_mål ON sr.målsag_id = ss_mål.id
            WHERE (sr.kilde_sag_id = %s OR sr.målsag_id = %s)
            AND sr.deleted_at IS NULL
            ORDER BY sr.created_at DESC
        """
        relationer = execute_query(relationer_query, (sag_id, sag_id))
        
        # --- Relation Tree Construction ---
        relation_tree = []
        try:
            from app.modules.sag.services.relation_service import RelationService
            relation_tree = RelationService.get_relation_tree(sag_id)
        except Exception as e:
            logger.error(f"Error building relation tree: {e}")
            relation_tree = []

        # Fallback: if tree builder fails/returns empty but relations exist, render a minimal flat tree.
        if not relation_tree and relationer:
            try:
                root_node = {
                    "case": {
                        "id": sag.get("id"),
                        "titel": sag.get("titel"),
                        "status": sag.get("status"),
                        "type": sag.get("type"),
                        "template_key": sag.get("template_key"),
                    },
                    "relation_type": None,
                    "relation_id": None,
                    "is_current": True,
                    "children": [],
                }

                seen_related = set()
                for rel in relationer or []:
                    source_id = rel.get("kilde_sag_id")
                    target_id = rel.get("målsag_id")
                    related_id = target_id if source_id == sag_id else source_id
                    if not related_id or related_id in seen_related:
                        continue
                    seen_related.add(related_id)

                    root_node["children"].append({
                        "case": {
                            "id": related_id,
                            "titel": rel.get("mål_titel") if source_id == sag_id else rel.get("kilde_titel"),
                            "status": None,
                            "type": None,
                            "template_key": None,
                        },
                        "relation_type": rel.get("relationstype") or "Relateret til",
                        "relation_id": rel.get("id"),
                        "is_current": False,
                        "children": [],
                    })

                relation_tree = [root_node]
            except Exception as fallback_err:
                logger.warning("⚠️ Could not build fallback relation tree: %s", fallback_err)
        
        # Fetch customer info if customer_id exists
        customer = None
        hovedkontakt = None
        if sag.get('customer_id'):
            customer_query = "SELECT * FROM customers WHERE id = %s"
            customer_result = execute_query(customer_query, (sag['customer_id'],))
            if customer_result:
                customer = customer_result[0]

        # Fetch hovedkontakt (primary contact) for case via sag_kontakter
        kontakt_query = """
            SELECT c.*
            FROM contacts c
            JOIN sag_kontakter sk ON c.id = sk.contact_id
            WHERE sk.sag_id = %s AND sk.deleted_at IS NULL AND sk.is_primary = TRUE
            LIMIT 1
        """
        kontakt_result = execute_query(kontakt_query, (sag_id,))
        if kontakt_result:
            hovedkontakt = kontakt_result[0]
        else:
            fallback_query = """
                SELECT c.*
                FROM contacts c
                JOIN sag_kontakter sk ON c.id = sk.contact_id
                WHERE sk.sag_id = %s AND sk.deleted_at IS NULL
                ORDER BY sk.created_at ASC
                LIMIT 1
            """
            fallback_result = execute_query(fallback_query, (sag_id,))
            if fallback_result:
                hovedkontakt = fallback_result[0]

        # Fetch prepaid cards for customer
        # Cast remaining_hours to float to avoid Jinja formatting issues with Decimal
        # DEBUG: Logging customer ID
        prepaid_cards = []
        if sag.get('customer_id'):
            cid = sag.get('customer_id')
            logger.info(f"🔎 Looking up prepaid cards for Sag {sag_id}, Customer ID: {cid} (Type: {type(cid)})")
            
            pc_query = """
                SELECT id, card_number, CAST(remaining_hours AS FLOAT) as remaining_hours, expires_at
                FROM tticket_prepaid_cards
                WHERE customer_id = %s
                  AND status = 'active'
                  AND remaining_hours > 0
                ORDER BY created_at DESC
            """
            prepaid_cards = execute_query(pc_query, (cid,))
            logger.info(f"💳 Found {len(prepaid_cards)} prepaid cards for customer {cid}")

        # Fetch fixed-price agreements for customer
        fixed_price_agreements = []
        if sag.get('customer_id'):
            cid = sag.get('customer_id')
            logger.info(f"🔎 Looking up fixed-price agreements for Sag {sag_id}, Customer ID: {cid}")
            
            fpa_query = """
                SELECT 
                    a.id, 
                    a.agreement_number, 
                    a.monthly_hours,
                    COALESCE(bp.remaining_hours, a.monthly_hours) as remaining_hours_this_month
                FROM customer_fixed_price_agreements a
                LEFT JOIN fixed_price_billing_periods bp ON (
                    a.id = bp.agreement_id 
                    AND bp.period_start <= CURRENT_DATE 
                    AND bp.period_end >= CURRENT_DATE
                )
                WHERE a.customer_id = %s
                  AND a.status = 'active'
                  AND (a.end_date IS NULL OR a.end_date >= CURRENT_DATE)
                ORDER BY a.created_at DESC
            """
            fixed_price_agreements = execute_query(fpa_query, (cid,))
            logger.info(f"📋 Found {len(fixed_price_agreements)} fixed-price agreements for customer {cid}")

        # Fetch Nextcloud Instance for this customer
        nextcloud_instance = None
        if customer:
            nc_query = "SELECT * FROM nextcloud_instances WHERE customer_id = %s AND deleted_at IS NULL"
            nc_result = execute_query(nc_query, (customer['id'],))
            if nc_result:
                nextcloud_instance = nc_result[0]

        # Fetch linked contacts
        contacts_query = """
            SELECT
                sk.*,
                c.first_name || ' ' || c.last_name as contact_name,
                c.email as contact_email,
                c.phone,
                c.mobile,
                c.title,
                company.customer_name
            FROM sag_kontakter sk
            JOIN contacts c ON sk.contact_id = c.id
            LEFT JOIN LATERAL (
                SELECT cu.name AS customer_name
                FROM contact_companies cc
                JOIN customers cu ON cu.id = cc.customer_id
                WHERE cc.contact_id = c.id
                ORDER BY cc.is_primary DESC, cu.name
                LIMIT 1
            ) company ON TRUE
            WHERE sk.sag_id = %s AND sk.deleted_at IS NULL
        """
        contacts = execute_query(contacts_query, (sag_id,))

        # Fetch linked customers
        customers_query = """
            SELECT sk.*, c.name as customer_name, c.email as customer_email
            FROM sag_kunder sk
            JOIN customers c ON sk.customer_id = c.id
            WHERE sk.sag_id = %s AND sk.deleted_at IS NULL
        """
        customers = execute_query(customers_query, (sag_id,))
        
        # Fetch comments
        comments_query = "SELECT * FROM sag_kommentarer WHERE sag_id = %s AND deleted_at IS NULL ORDER BY created_at DESC"
        comments = execute_query(comments_query, (sag_id,))

        # Fetch Solution
        solution_query = "SELECT * FROM sag_solutions WHERE sag_id = %s"
        solution_res = execute_query(solution_query, (sag_id,))
        solution = solution_res[0] if solution_res else None

        # Fetch Time Entries
        time_query = "SELECT * FROM tmodule_times WHERE sag_id = %s ORDER BY worked_date DESC"
        time_entries = execute_query(time_query, (sag_id,))

        # Fetch linked telephony call history
        call_history_query = """
            SELECT
                t.id,
                t.callid,
                t.direction,
                t.ekstern_nummer,
                t.started_at,
                t.ended_at,
                t.duration_sec,
                u.username,
                u.full_name,
                CONCAT(COALESCE(c.first_name, ''), ' ', COALESCE(c.last_name, '')) AS contact_name
            FROM telefoni_opkald t
            LEFT JOIN users u ON u.user_id = t.bruger_id
            LEFT JOIN contacts c ON c.id = t.kontakt_id
            WHERE t.sag_id = %s
            ORDER BY t.started_at DESC
            LIMIT 200
        """
        call_history = execute_query(call_history_query, (sag_id,))

        # Check for nextcloud integration (case-insensitive, insensitive to whitespace)
        logger.info(f"Checking tags for Nextcloud on case {sag_id}: {tags}")
        is_nextcloud = any(t['tag_navn'] and t['tag_navn'].strip().lower() == 'nextcloud' for t in tags)
        logger.info(f"is_nextcloud result: {is_nextcloud}")

        related_case_options = []
        try:
            related_ids = set()
            for rel in relationer or []:
                related_ids.add(rel["kilde_sag_id"])
                related_ids.add(rel["målsag_id"])
            related_ids.discard(sag_id)
            if related_ids:
                placeholders = ",".join(["%s"] * len(related_ids))
                related_query = f"SELECT id, titel, status FROM sag_sager WHERE id IN ({placeholders}) AND deleted_at IS NULL"
                related_case_options = execute_query(related_query, tuple(related_ids))
        except Exception as e:
            logger.error("❌ Error building related case options: %s", e)
            related_case_options = []

        pipeline_stages = []
        try:
            pipeline_stages = execute_query(
                "SELECT id, name, color, sort_order FROM pipeline_stages ORDER BY sort_order ASC, id ASC",
                (),
            )
        except Exception as e:
            logger.warning("⚠️ Could not load pipeline stages: %s", e)
            pipeline_stages = []

        status_options = _fetch_case_status_options()
        current_status = str(sag.get("status") or "").strip()
        if current_status and current_status.lower() not in {s.lower() for s in status_options}:
            status_options.append(current_status)
        is_deadline_overdue = _is_deadline_overdue(sag.get("deadline"))

        return templates.TemplateResponse("modules/sag/templates/detail_v3.html", {
            "request": request,
            "case": sag,
            "customer": customer,
            "hovedkontakt": hovedkontakt,
            "contacts": contacts,
            "customers": customers,
            "prepaid_cards": prepaid_cards,
            "fixed_price_agreements": fixed_price_agreements,
            "tags": tags,

            "relationer": relationer,
            "relation_tree": relation_tree,
            "comments": comments,
            "solution": solution,
            "time_entries": time_entries,
            "call_history": call_history,
            "is_nextcloud": is_nextcloud,
            "nextcloud_instance": nextcloud_instance,
            "related_case_options": related_case_options,
            "pipeline_stages": pipeline_stages,
            "status_options": status_options,
            "is_deadline_overdue": is_deadline_overdue,
            "assignment_users": _fetch_assignment_users(),
            "assignment_groups": _fetch_assignment_groups(),
        })
    except HTTPException:
        raise
    except Exception as e:
        logger.error("❌ Error displaying case details: %s", e)
        raise HTTPException(status_code=500, detail="Failed to load case details")


@router.get("/sag/{sag_id}/edit", response_class=HTMLResponse)
async def sag_rediger(request: Request, sag_id: int):
    """Display edit case form."""
    try:
        sag_query = "SELECT * FROM sag_sager WHERE id = %s AND deleted_at IS NULL"
        sag_result = execute_query(sag_query, (sag_id,))

        if not sag_result:
            raise HTTPException(status_code=404, detail="Case not found")

        return templates.TemplateResponse("modules/sag/templates/edit.html", {
            "request": request,
            "case": sag_result[0],
            "assignment_users": _fetch_assignment_users(),
            "assignment_groups": _fetch_assignment_groups(),
        })
    except HTTPException:
        raise
    except Exception as e:
        logger.error("❌ Error loading edit case page: %s", e)
        raise HTTPException(status_code=500, detail="Failed to load edit case page")
