"""
Tag system API endpoints
"""
from fastapi import APIRouter, HTTPException, Query
from typing import List, Optional
import json
import re
from app.tags.backend.models import (
    Tag, TagCreate, TagUpdate, 
    EntityTag, EntityTagCreate,
    TagWorkflow, TagWorkflowCreate,
    TagType,
    TagGroup, TagGroupCreate
)
from app.core.database import execute_query, execute_query_single, execute_update

router = APIRouter(prefix="/tags")

MODULE_LABELS = {
    "case": "Sager",
    "email": "Email",
    "ticket": "Tickets",
    "customer": "Kunder",
    "contact": "Kontakter",
    "time_entry": "Tid",
    "order": "Ordrer",
    "comment": "Ticket kommentarer",
    "worklog": "Ticket worklog",
}


def _module_label_for_entity_type(entity_type: Optional[str]) -> str:
    key = str(entity_type or "").strip().lower()
    if not key:
        return "Ukendt modul"
    return MODULE_LABELS.get(key, f"Ukendt modul ({key})")


def _entity_reference_payload(entity_type: Optional[str], entity_id: Optional[int]) -> dict:
    etype = str(entity_type or "").strip().lower()
    eid = int(entity_id or 0)
    default_label = f"#{eid}" if eid else "Ukendt"

    if not etype or not eid:
        return {"entity_title": default_label, "entity_url": None}

    try:
        if etype == "case":
            row = execute_query_single(
                "SELECT id, titel FROM sag_sager WHERE id = %s AND deleted_at IS NULL",
                (eid,),
            )
            if row:
                title = str(row.get("titel") or "Sag").strip()
                return {"entity_title": title, "entity_url": f"/sag/{eid}/v3"}

        elif etype == "email":
            row = execute_query_single(
                "SELECT id, subject FROM email_messages WHERE id = %s AND deleted_at IS NULL",
                (eid,),
            )
            if row:
                title = str(row.get("subject") or "Email").strip()
                return {"entity_title": title, "entity_url": f"/emails?id={eid}"}

        elif etype == "ticket":
            row = execute_query_single(
                "SELECT id, ticket_number, subject FROM tticket_tickets WHERE id = %s",
                (eid,),
            )
            if row:
                ticket_number = str(row.get("ticket_number") or "").strip()
                subject = str(row.get("subject") or "Ticket").strip()
                title = f"{ticket_number} - {subject}" if ticket_number else subject
                return {"entity_title": title, "entity_url": f"/ticket/tickets/{eid}"}

        elif etype == "customer":
            row = execute_query_single("SELECT id, name FROM customers WHERE id = %s", (eid,))
            if row:
                title = str(row.get("name") or "Kunde").strip()
                return {"entity_title": title, "entity_url": f"/customers/{eid}"}

        elif etype == "contact":
            row = execute_query_single(
                "SELECT id, first_name, last_name, email FROM contacts WHERE id = %s",
                (eid,),
            )
            if row:
                name = " ".join(
                    [str(row.get("first_name") or "").strip(), str(row.get("last_name") or "").strip()]
                ).strip()
                title = name or str(row.get("email") or "Kontakt").strip()
                return {"entity_title": title, "entity_url": f"/contacts/{eid}"}

        elif etype == "time_entry":
            row = execute_query_single(
                "SELECT id, description, worked_date FROM tmodule_times WHERE id = %s",
                (eid,),
            )
            if row:
                description = str(row.get("description") or "Tidsregistrering").strip()
                return {"entity_title": description[:90], "entity_url": "/timetracking/registrations"}

        elif etype == "order":
            row = execute_query_single(
                "SELECT id, order_number, total_amount FROM tmodule_orders WHERE id = %s",
                (eid,),
            )
            if row:
                order_number = str(row.get("order_number") or "Ordre").strip()
                total_amount = row.get("total_amount")
                suffix = f" ({total_amount} kr.)" if total_amount is not None else ""
                return {"entity_title": f"{order_number}{suffix}", "entity_url": "/timetracking/orders"}

        elif etype == "worklog":
            row = execute_query_single(
                """
                SELECT w.id, w.description, w.ticket_id, t.ticket_number
                FROM tticket_worklog w
                LEFT JOIN tticket_tickets t ON t.id = w.ticket_id
                WHERE w.id = %s
                """,
                (eid,),
            )
            if row:
                ticket_id = row.get("ticket_id")
                ticket_number = str(row.get("ticket_number") or "Ticket").strip()
                description = str(row.get("description") or "Worklog").strip()
                url = f"/ticket/tickets/{ticket_id}" if ticket_id else None
                return {"entity_title": f"{ticket_number} - {description[:70]}", "entity_url": url}

        elif etype == "comment":
            row = execute_query_single(
                """
                SELECT c.id, c.comment_text, c.ticket_id, t.ticket_number
                FROM tticket_comments c
                LEFT JOIN tticket_tickets t ON t.id = c.ticket_id
                WHERE c.id = %s
                """,
                (eid,),
            )
            if row:
                ticket_id = row.get("ticket_id")
                ticket_number = str(row.get("ticket_number") or "Ticket").strip()
                comment_text = str(row.get("comment_text") or "Kommentar").strip()
                url = f"/ticket/tickets/{ticket_id}" if ticket_id else None
                return {"entity_title": f"{ticket_number} - {comment_text[:70]}", "entity_url": url}
    except Exception:
        pass

    return {"entity_title": default_label, "entity_url": None}


def _normalize_catch_words(value) -> List[str]:
    """Normalize catch words from JSON/text/list to a clean lowercase list."""
    if value is None:
        return []
    if isinstance(value, list):
        words = value
    elif isinstance(value, str):
        stripped = value.strip()
        if not stripped:
            return []
        if stripped.startswith("["):
            try:
                parsed = json.loads(stripped)
                words = parsed if isinstance(parsed, list) else []
            except Exception:
                words = [w.strip() for w in stripped.replace("\n", ",").split(",")]
        else:
            words = [w.strip() for w in stripped.replace("\n", ",").split(",")]
    else:
        words = []

    cleaned = []
    seen = set()
    for word in words:
        normalized = str(word or "").strip().lower()
        if len(normalized) < 2:
            continue
        if normalized in seen:
            continue
        seen.add(normalized)
        cleaned.append(normalized)
    return cleaned


def _tag_row_to_response(row: dict) -> dict:
    """Ensure API response always exposes catch_words as a list."""
    if not row:
        return row
    out = dict(row)

    valid_types = {"workflow", "status", "category", "priority", "billing", "brand", "type"}
    tag_type = str(out.get("type") or "").strip().lower()
    if tag_type not in valid_types:
        tag_type = "category"
    out["type"] = tag_type

    color = str(out.get("color") or "").strip()
    if not re.fullmatch(r"#[0-9A-Fa-f]{6}", color):
        out["color"] = "#0f4c75"

    if not out.get("name"):
        out["name"] = "Unnamed tag"

    out["catch_words"] = _normalize_catch_words(out.get("catch_words"))
    return out


def _resolve_tag_action_for_add(tag_id: int) -> Optional[dict]:
    workflow = execute_query_single(
        """
        SELECT action_type, COALESCE(action_config, '{}'::jsonb) AS action_config
        FROM tag_workflows
        WHERE tag_id = %s
          AND trigger_event = 'on_add'
          AND is_active = TRUE
        ORDER BY id DESC
        LIMIT 1
        """,
        (tag_id,),
    )
    if not workflow:
        return None

    return {
        "type": workflow.get("action_type"),
        "config": workflow.get("action_config") or {},
    }

# ============= TAG GROUPS =============

@router.get("/groups", response_model=List[TagGroup])
async def list_tag_groups():
    results = execute_query("SELECT * FROM tag_groups ORDER BY name")
    return results


@router.post("/groups", response_model=TagGroup)
async def create_tag_group(group: TagGroupCreate):
    query = """
        INSERT INTO tag_groups (name, description, behavior)
        VALUES (%s, %s, %s)
        RETURNING *
    """
    result = execute_query_single(query, (group.name, group.description, group.behavior))
    return result

# ============= TAG CRUD =============

@router.get("/usage")
async def list_tag_usage(
    tag_name: Optional[str] = Query(None),
    tag_type: Optional[TagType] = Query(None),
    module: Optional[str] = Query(None),
    page: int = Query(1, ge=1),
    page_size: int = Query(25, ge=1, le=200),
    sort_by: str = Query("tagged_at"),
    sort_dir: str = Query("desc"),
):
    """List tag usage across modules with server-side filtering and pagination."""
    where_parts = ["1=1"]
    params: List[object] = []

    if tag_name:
        where_parts.append("LOWER(t.name) LIKE LOWER(%s)")
        params.append(f"%{tag_name.strip()}%")

    if tag_type:
        where_parts.append("t.type = %s")
        params.append(tag_type)

    if module:
        where_parts.append("LOWER(et.entity_type) = LOWER(%s)")
        params.append(module.strip())

    where_clause = " AND ".join(where_parts)

    sortable = {
        "tagged_at": "et.tagged_at",
        "tag_name": "t.name",
        "tag_type": "t.type",
        "module": "et.entity_type",
        "entity_id": "et.entity_id",
    }
    order_column = sortable.get(sort_by, "et.tagged_at")
    order_direction = "ASC" if str(sort_dir).lower() == "asc" else "DESC"

    count_query = f"""
        SELECT COUNT(*) AS total
        FROM entity_tags et
        JOIN tags t ON t.id = et.tag_id
        WHERE {where_clause}
    """
    count_row = execute_query_single(count_query, tuple(params)) or {"total": 0}
    total = int(count_row.get("total") or 0)

    offset = (page - 1) * page_size
    data_query = f"""
        SELECT
            et.id AS entity_tag_id,
            et.entity_type,
            et.entity_id,
            et.tagged_at,
            t.id AS tag_id,
            t.name AS tag_name,
            t.type AS tag_type,
            t.color AS tag_color,
            t.is_active AS tag_is_active
        FROM entity_tags et
        JOIN tags t ON t.id = et.tag_id
        WHERE {where_clause}
        ORDER BY {order_column} {order_direction}, et.id DESC
        LIMIT %s OFFSET %s
    """

    rows = execute_query(data_query, tuple(params + [page_size, offset])) or []
    items = []
    for row in rows:
        entity_type = row.get("entity_type")
        entity_ref = _entity_reference_payload(entity_type, row.get("entity_id"))
        items.append(
            {
                "entity_tag_id": row.get("entity_tag_id"),
                "tag_id": row.get("tag_id"),
                "tag_name": row.get("tag_name"),
                "tag_type": row.get("tag_type"),
                "tag_color": row.get("tag_color"),
                "tag_is_active": bool(row.get("tag_is_active")),
                "module": _module_label_for_entity_type(entity_type),
                "entity_type": entity_type,
                "entity_id": row.get("entity_id"),
                "entity_title": entity_ref.get("entity_title"),
                "entity_url": entity_ref.get("entity_url"),
                "tagged_at": row.get("tagged_at"),
            }
        )

    module_rows = execute_query(
        "SELECT DISTINCT entity_type FROM entity_tags ORDER BY entity_type",
        (),
    ) or []
    module_options = [
        {
            "value": row.get("entity_type"),
            "label": _module_label_for_entity_type(row.get("entity_type")),
        }
        for row in module_rows
    ]

    return {
        "items": items,
        "pagination": {
            "page": page,
            "page_size": page_size,
            "total": total,
            "total_pages": (total + page_size - 1) // page_size if total else 0,
        },
        "sort": {"sort_by": sort_by, "sort_dir": order_direction.lower()},
        "module_options": module_options,
    }

@router.get("", response_model=List[Tag])
async def list_tags(
    type: Optional[TagType] = None,
    is_active: Optional[bool] = None
):
    """List all tags with optional filtering"""
    query = """
        SELECT id, name, type, description, color, icon, is_active, tag_group_id,
               COALESCE(catch_words_json, '[]'::jsonb) AS catch_words,
               created_at, updated_at
        FROM tags
        WHERE 1=1
    """
    params = []
    
    if type:
        query += " AND type = %s"
        params.append(type)
    
    if is_active is not None:
        query += " AND is_active = %s"
        params.append(is_active)
    
    query += " ORDER BY type, name"
    
    results = execute_query(query, tuple(params) if params else ())
    return [_tag_row_to_response(row) for row in (results or [])]

@router.get("/{tag_id}", response_model=Tag)
async def get_tag(tag_id: int):
    """Get single tag by ID"""
    result = execute_query_single(
        """
        SELECT id, name, type, description, color, icon, is_active, tag_group_id,
               COALESCE(catch_words_json, '[]'::jsonb) AS catch_words,
               created_at, updated_at
        FROM tags
        WHERE id = %s
        """,
        (tag_id,)
    )
    if not result:
        raise HTTPException(status_code=404, detail="Tag not found")
    return _tag_row_to_response(result)

@router.post("", response_model=Tag)
async def create_tag(tag: TagCreate):
    """Create new tag"""
    query = """
        INSERT INTO tags (name, type, description, color, icon, is_active, tag_group_id, catch_words_json)
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s::jsonb)
        RETURNING id, name, type, description, color, icon, is_active, tag_group_id,
                  COALESCE(catch_words_json, '[]'::jsonb) AS catch_words,
                  created_at, updated_at
    """
    catch_words = _normalize_catch_words(tag.catch_words)
    result = execute_query_single(
        query,
        (
            tag.name,
            tag.type,
            tag.description,
            tag.color,
            tag.icon,
            tag.is_active,
            tag.tag_group_id,
            json.dumps(catch_words),
        )
    )
    if not result:
        raise HTTPException(status_code=500, detail="Failed to create tag")
    return _tag_row_to_response(result)

@router.put("/{tag_id}", response_model=Tag)
async def update_tag(tag_id: int, tag: TagUpdate):
    """Update existing tag"""
    # Build dynamic update query
    updates = []
    params = []
    
    if tag.name is not None:
        updates.append("name = %s")
        params.append(tag.name)
    if tag.description is not None:
        updates.append("description = %s")
        params.append(tag.description)
    if tag.color is not None:
        updates.append("color = %s")
        params.append(tag.color)
    if tag.icon is not None:
        updates.append("icon = %s")
        params.append(tag.icon)
    if tag.is_active is not None:
        updates.append("is_active = %s")
        params.append(tag.is_active)
    if tag.tag_group_id is not None:
        updates.append("tag_group_id = %s")
        params.append(tag.tag_group_id)
    if tag.catch_words is not None:
        updates.append("catch_words_json = %s::jsonb")
        params.append(json.dumps(_normalize_catch_words(tag.catch_words)))
    
    if not updates:
        raise HTTPException(status_code=400, detail="No fields to update")
    
    updates.append("updated_at = CURRENT_TIMESTAMP")
    params.append(tag_id)
    
    query = f"""
        UPDATE tags 
        SET {', '.join(updates)}
        WHERE id = %s
        RETURNING id, name, type, description, color, icon, is_active, tag_group_id,
                  COALESCE(catch_words_json, '[]'::jsonb) AS catch_words,
                  created_at, updated_at
    """
    
    result = execute_query_single(query, tuple(params))
    if not result:
        raise HTTPException(status_code=404, detail="Tag not found")
    return _tag_row_to_response(result)

@router.delete("/{tag_id}")
async def delete_tag(tag_id: int):
    """Delete tag (also removes all entity associations)"""
    result = execute_update(
        "DELETE FROM tags WHERE id = %s",
        (tag_id,)
    )
    if result == 0:
        raise HTTPException(status_code=404, detail="Tag not found")
    return {"message": "Tag deleted successfully"}


# ============= ENTITY TAGGING =============

@router.post("/entity", response_model=EntityTag)
async def add_tag_to_entity(entity_tag: EntityTagCreate):
    """Add tag to any entity (ticket, customer, time_entry, etc.)"""
    # Enforce toggle/single groups
    group = execute_query_single(
        """
        SELECT tg.behavior, t.tag_group_id
        FROM tags t
        JOIN tag_groups tg ON t.tag_group_id = tg.id
        WHERE t.id = %s
        """,
        (entity_tag.tag_id,)
    )

    if group and group.get("behavior") in ("single", "toggle"):
        execute_update(
            """
            DELETE FROM entity_tags
            WHERE entity_type = %s
              AND entity_id = %s
              AND tag_id IN (
                  SELECT id FROM tags WHERE tag_group_id = %s
              )
            """,
            (entity_tag.entity_type, entity_tag.entity_id, group["tag_group_id"])
        )

    query = """
        INSERT INTO entity_tags (entity_type, entity_id, tag_id, tagged_by)
        VALUES (%s, %s, %s, %s)
        ON CONFLICT (entity_type, entity_id, tag_id) DO NOTHING
        RETURNING *
    """
    result = execute_query_single(
        query,
        (entity_tag.entity_type, entity_tag.entity_id, entity_tag.tag_id, entity_tag.tagged_by)
    )
    if not result:
        raise HTTPException(status_code=409, detail="Tag already exists on entity")

    action = _resolve_tag_action_for_add(entity_tag.tag_id)
    if action:
        result = dict(result)
        result["action"] = action

    return result

@router.delete("/entity")
async def remove_tag_from_entity(
    entity_type: str,
    entity_id: int,
    tag_id: int
):
    """Remove tag from entity"""
    result = execute_update(
        "DELETE FROM entity_tags WHERE entity_type = %s AND entity_id = %s AND tag_id = %s",
        (entity_type, entity_id, tag_id)
    )
    if result == 0:
        raise HTTPException(status_code=404, detail="Tag association not found")
    return {"message": "Tag removed from entity"}

@router.delete("/entity/{entity_type}/{entity_id}/{tag_id}")
async def remove_tag_from_entity_path(
    entity_type: str,
    entity_id: int,
    tag_id: int
):
    """Remove tag from entity (Path param version)"""
    result = execute_update(
        "DELETE FROM entity_tags WHERE entity_type = %s AND entity_id = %s AND tag_id = %s",
        (entity_type, entity_id, tag_id)
    )
    if result == 0:
        raise HTTPException(status_code=404, detail="Tag association not found")
    return {"message": "Tag removed from entity"}

@router.get("/entity/{entity_type}/{entity_id}", response_model=List[Tag])
async def get_entity_tags(entity_type: str, entity_id: int):
    """Get all tags for a specific entity"""
    query = """
        SELECT t.id, t.name, t.type, t.description, t.color, t.icon, t.is_active, t.tag_group_id,
               COALESCE(t.catch_words_json, '[]'::jsonb) AS catch_words,
               t.created_at, t.updated_at
        FROM tags t
        JOIN entity_tags et ON et.tag_id = t.id
        WHERE et.entity_type = %s AND et.entity_id = %s
        ORDER BY t.type, t.name
    """
    results = execute_query(query, (entity_type, entity_id))
    return [_tag_row_to_response(row) for row in (results or [])]


@router.get("/entity/{entity_type}/{entity_id}/suggestions")
async def suggest_entity_tags(entity_type: str, entity_id: int):
    """Suggest tags based on catch words for brand/type tags."""
    if entity_type != "case":
        return []

    case_row = execute_query_single(
        "SELECT id, titel, beskrivelse, template_key FROM sag_sager WHERE id = %s",
        (entity_id,),
    )
    if not case_row:
        raise HTTPException(status_code=404, detail="Entity not found")

    existing_rows = execute_query(
        "SELECT tag_id FROM entity_tags WHERE entity_type = %s AND entity_id = %s",
        (entity_type, entity_id),
    ) or []
    existing_tag_ids = {int(row.get("tag_id")) for row in existing_rows if row.get("tag_id") is not None}

    candidate_rows = execute_query(
        """
        SELECT id, name, type, description, color, icon, is_active, tag_group_id,
               COALESCE(catch_words_json, '[]'::jsonb) AS catch_words,
               created_at, updated_at
        FROM tags
        WHERE is_active = true
          AND type IN ('brand', 'type')
        ORDER BY type, name
        """,
        (),
    ) or []

    haystack = " ".join(
        [
            str(case_row.get("titel") or ""),
            str(case_row.get("beskrivelse") or ""),
            str(case_row.get("template_key") or ""),
        ]
    ).lower()

    suggestions = []
    for row in candidate_rows:
        tag_id = int(row.get("id"))
        if tag_id in existing_tag_ids:
            continue

        catch_words = _normalize_catch_words(row.get("catch_words"))
        if not catch_words:
            continue

        matched_words = [word for word in catch_words if word in haystack]
        if not matched_words:
            continue

        suggestions.append(
            {
                "tag": _tag_row_to_response(row),
                "matched_words": matched_words,
                "score": len(matched_words),
            }
        )

    suggestions.sort(key=lambda item: (-item["score"], item["tag"]["type"], item["tag"]["name"]))
    return suggestions

@router.get("/search")
async def search_tags(q: str, type: Optional[TagType] = None):
    """Search tags by name (fuzzy search)"""
    query = """
        SELECT id, name, type, description, color, icon, is_active, tag_group_id,
               COALESCE(catch_words_json, '[]'::jsonb) AS catch_words,
               created_at, updated_at
        FROM tags
        WHERE is_active = true 
        AND LOWER(name) LIKE LOWER(%s)
    """
    params = [f"%{q}%"]
    
    if type:
        query += " AND type = %s"
        params.append(type)
    
    query += " ORDER BY name LIMIT 20"
    
    results = execute_query(query, tuple(params))
    return [_tag_row_to_response(row) for row in (results or [])]


# ============= WORKFLOW MANAGEMENT =============

@router.get("/workflows", response_model=List[TagWorkflow])
async def list_workflows(tag_id: Optional[int] = None):
    """List all workflow configurations"""
    if tag_id:
        results = execute_query(
            "SELECT * FROM tag_workflows WHERE tag_id = %s ORDER BY id",
            (tag_id,)
        )
    else:
        results = execute_query(
            "SELECT * FROM tag_workflows ORDER BY tag_id, id",
            ()
        )
    return results

@router.post("/workflows", response_model=TagWorkflow)
async def create_workflow(workflow: TagWorkflowCreate):
    """Create new workflow trigger"""
    query = """
        INSERT INTO tag_workflows (tag_id, trigger_event, action_type, action_config, is_active)
        VALUES (%s, %s, %s, %s, %s)
        RETURNING *
    """
    result = execute_query_single(
        query,
        (workflow.tag_id, workflow.trigger_event, workflow.action_type, 
         workflow.action_config, workflow.is_active)
    )
    return result

@router.delete("/workflows/{workflow_id}")
async def delete_workflow(workflow_id: int):
    """Delete workflow configuration"""
    result = execute_update(
        "DELETE FROM tag_workflows WHERE id = %s",
        (workflow_id,)
    )
    if result == 0:
        raise HTTPException(status_code=404, detail="Workflow not found")
    return {"message": "Workflow deleted"}
