"""Normalize and filter Jira webhook payloads."""

from __future__ import annotations

import hashlib
import hmac
import re
from dataclasses import dataclass
from typing import Any, Dict, Optional

from packages.config import Settings, get_settings
from packages.db.models import WorkflowState


@dataclass
class NormalizedWebhookEvent:
    delivery_id: str
    event_type: str
    jira_key: str
    project_key: str
    status: str
    issue_type: str
    labels: list[str]
    comment_body: Optional[str]
    comment_author_id: Optional[str]
    raw: Dict[str, Any]

    @property
    def is_comment(self) -> bool:
        return self.event_type in {"comment_created", "comment_updated"}


def validate_webhook_signature(
    body: bytes,
    signature_header: Optional[str],
    secret: str,
) -> bool:
    """Validate optional HMAC SHA256 signature. If no header provided in local/mock, allow."""
    if not signature_header:
        return True
    digest = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
    expected = f"sha256={digest}"
    return hmac.compare_digest(expected, signature_header)


def normalize_jira_webhook(payload: Dict[str, Any], delivery_id: str) -> NormalizedWebhookEvent:
    event_type = payload.get("webhookEvent") or payload.get("issue_event_type_name") or "unknown"
    issue = payload.get("issue") or {}
    fields = issue.get("fields") or {}
    comment = payload.get("comment") or {}
    key = issue.get("key") or ""
    if not key and comment:
        # some payloads nest issue elsewhere
        key = (payload.get("issue") or {}).get("key") or ""

    return NormalizedWebhookEvent(
        delivery_id=delivery_id,
        event_type=event_type,
        jira_key=key,
        project_key=((fields.get("project") or {}).get("key") or key.split("-")[0] if key else ""),
        status=((fields.get("status") or {}).get("name") or ""),
        issue_type=((fields.get("issuetype") or {}).get("name") or ""),
        labels=list(fields.get("labels") or []),
        comment_body=_comment_text(comment),
        comment_author_id=((comment.get("author") or {}).get("accountId")),
        raw=payload,
    )


def _comment_text(comment: Dict[str, Any]) -> Optional[str]:
    if not comment:
        return None
    body = comment.get("body")
    if isinstance(body, str):
        return body
    if isinstance(body, dict):
        # ADF simplified
        texts = []

        def walk(node: Any) -> None:
            if isinstance(node, dict):
                if node.get("type") == "text" and node.get("text"):
                    texts.append(node["text"])
                for c in node.get("content") or []:
                    walk(c)
            elif isinstance(node, list):
                for c in node:
                    walk(c)

        walk(body)
        return "\n".join(texts) if texts else None
    return None


def is_agent_ready(event: NormalizedWebhookEvent, settings: Optional[Settings] = None) -> bool:
    settings = settings or get_settings()
    if event.project_key not in settings.jira_project_key_list:
        return False
    if event.issue_type and event.issue_type not in settings.jira_issue_type_list:
        return False
    label_ok = settings.jira_agent_label in (event.labels or [])
    status_ok = event.status == settings.jira_agent_status
    return label_ok or status_ok


def should_start_run(event: NormalizedWebhookEvent, settings: Optional[Settings] = None) -> bool:
    if event.is_comment:
        return False
    return is_agent_ready(event, settings)


def should_resume_clarification(
    event: NormalizedWebhookEvent,
    current_state: Optional[WorkflowState],
    settings: Optional[Settings] = None,
) -> bool:
    settings = settings or get_settings()
    if not event.is_comment:
        return False
    if current_state != WorkflowState.NEEDS_CLARIFICATION:
        return False
    if not event.comment_body or not event.comment_body.strip():
        return False
    bot_id = settings.jira_bot_account_id
    if bot_id and event.comment_author_id == bot_id:
        return False
    # Ignore our own clarification comments
    if "Agent Nova needs clarification" in event.comment_body:
        return False
    return True


def is_design_reject_comment(body: Optional[str]) -> bool:
    if not body or not body.strip():
        return False
    return bool(re.match(r"^(reject|rejected)\b", body.strip(), re.IGNORECASE))


def should_resume_design_review(
    event: NormalizedWebhookEvent,
    current_state: Optional[WorkflowState],
    settings: Optional[Settings] = None,
) -> bool:
    """True when a human comment should resume a run paused in DESIGN_REVIEW."""
    settings = settings or get_settings()
    if not event.is_comment:
        return False
    if current_state != WorkflowState.DESIGN_REVIEW:
        return False
    if not event.comment_body or not event.comment_body.strip():
        return False
    bot_id = settings.jira_bot_account_id
    if bot_id and event.comment_author_id == bot_id:
        return False
    body = event.comment_body
    if "Agent Nova design ready for review" in body:
        return False
    if body.lstrip().startswith("## Confluence design doc") or body.lstrip().startswith(
        "Confluence design doc"
    ):
        return False
    return True


def design_review_decision(event: NormalizedWebhookEvent) -> str:
    """Return 'reject' or 'approve' for a design-review resume comment."""
    if is_design_reject_comment(event.comment_body):
        return "reject"
    return "approve"


def is_duplicate_close_comment(body: Optional[str]) -> bool:
    if not body or not body.strip():
        return False
    text = body.strip()
    if re.search(r"\bclose\s+as\s+duplicate\b", text, re.IGNORECASE):
        return True
    return bool(re.search(r"\bduplicate\b", text, re.IGNORECASE)) and not re.search(
        r"\bcontinue\b", text, re.IGNORECASE
    )


def is_duplicate_continue_comment(body: Optional[str]) -> bool:
    if not body or not body.strip():
        return False
    return bool(re.search(r"\bcontinue\b", body.strip(), re.IGNORECASE))


def duplicate_review_decision(event: NormalizedWebhookEvent) -> Optional[str]:
    """Return 'close', 'continue', or None if the comment is not a decision."""
    body = event.comment_body
    if is_duplicate_close_comment(body):
        return "close"
    if is_duplicate_continue_comment(body):
        return "continue"
    return None


def should_resume_duplicate_review(
    event: NormalizedWebhookEvent,
    current_state: Optional[WorkflowState],
    settings: Optional[Settings] = None,
) -> bool:
    """True when a human comment should resume a run paused in DUPLICATE_REVIEW."""
    settings = settings or get_settings()
    if not event.is_comment:
        return False
    if current_state != WorkflowState.DUPLICATE_REVIEW:
        return False
    if not event.comment_body or not event.comment_body.strip():
        return False
    bot_id = settings.jira_bot_account_id
    if bot_id and event.comment_author_id == bot_id:
        return False
    if "Agent Nova possible duplicate detected" in event.comment_body:
        return False
    return duplicate_review_decision(event) is not None


def is_doc_close_comment(body: Optional[str]) -> bool:
    if not body or not body.strip():
        return False
    text = body.strip()
    if re.search(r"\balready\s+done\b", text, re.IGNORECASE):
        return True
    # bare "close" but not "close as duplicate" (that belongs to duplicate review)
    if re.search(r"\bclose\s+as\s+duplicate\b", text, re.IGNORECASE):
        return False
    return bool(re.search(r"\bclose\b", text, re.IGNORECASE)) and not re.search(
        r"\bcontinue\b", text, re.IGNORECASE
    )


def is_doc_continue_comment(body: Optional[str]) -> bool:
    if not body or not body.strip():
        return False
    return bool(re.search(r"\bcontinue\b", body.strip(), re.IGNORECASE))


def doc_review_decision(event: NormalizedWebhookEvent) -> Optional[str]:
    """Return 'close', 'continue', or None for DOC_REVIEW resume comments."""
    body = event.comment_body
    if is_doc_close_comment(body):
        return "close"
    if is_doc_continue_comment(body):
        return "continue"
    return None


def should_resume_doc_review(
    event: NormalizedWebhookEvent,
    current_state: Optional[WorkflowState],
    settings: Optional[Settings] = None,
) -> bool:
    """True when a human comment should resume a run paused in DOC_REVIEW."""
    settings = settings or get_settings()
    if not event.is_comment:
        return False
    if current_state != WorkflowState.DOC_REVIEW:
        return False
    if not event.comment_body or not event.comment_body.strip():
        return False
    bot_id = settings.jira_bot_account_id
    if bot_id and event.comment_author_id == bot_id:
        return False
    if "functionality may already exist in Confluence" in event.comment_body:
        return False
    return doc_review_decision(event) is not None
