"""
Jira REST API v3 client — async, using httpx.
All methods return plain dicts (no Jira SDK objects).
Authentication: Basic auth with email + API token (Jira Cloud standard).
Docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/
"""
import base64
import logging
from typing import Optional
import httpx

from backend.config import (
    JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN, ENABLE_JIRA
)

logger = logging.getLogger("thirdeye.integrations.jira")

# ─── Auth ────────────────────────────────────────────────────────────────────

def _auth_header() -> dict:
    """Build the Basic auth header from email + API token."""
    raw = f"{JIRA_EMAIL}:{JIRA_API_TOKEN}"
    encoded = base64.b64encode(raw.encode()).decode()
    return {
        "Authorization": f"Basic {encoded}",
        "Accept": "application/json",
        "Content-Type": "application/json",
    }


def _base_url() -> str:
    return f"{JIRA_BASE_URL}/rest/api/3"


def is_configured() -> bool:
    """Return True if all required Jira config is set."""
    return bool(JIRA_BASE_URL and JIRA_EMAIL and JIRA_API_TOKEN and ENABLE_JIRA)


# ─── Core HTTP helpers ───────────────────────────────────────────────────────

async def _get(path: str, params: dict = None) -> dict:
    async with httpx.AsyncClient(timeout=15.0) as client:
        resp = await client.get(
            f"{_base_url()}{path}",
            headers=_auth_header(),
            params=params or {},
        )
        resp.raise_for_status()
        return resp.json()


async def _post(path: str, body: dict) -> dict:
    async with httpx.AsyncClient(timeout=15.0) as client:
        resp = await client.post(
            f"{_base_url()}{path}",
            headers=_auth_header(),
            json=body,
        )
        resp.raise_for_status()
        return resp.json()


async def _put(path: str, body: dict) -> dict:
    async with httpx.AsyncClient(timeout=15.0) as client:
        resp = await client.put(
            f"{_base_url()}{path}",
            headers=_auth_header(),
            json=body,
        )
        resp.raise_for_status()
        # PUT /issue returns 204 No Content on success
        if resp.status_code == 204:
            return {"ok": True}
        return resp.json()


# ─── Public API ──────────────────────────────────────────────────────────────

async def test_connection() -> dict:
    """
    Verify credentials work by calling /myself.
    Returns {"ok": True, "displayName": "...", "email": "..."} or {"ok": False, "error": "..."}
    """
    try:
        data = await _get("/myself")
        return {
            "ok": True,
            "display_name": data.get("displayName", "Unknown"),
            "email": data.get("emailAddress", "Unknown"),
            "account_id": data.get("accountId", ""),
        }
    except httpx.HTTPStatusError as e:
        return {"ok": False, "error": f"HTTP {e.response.status_code}: {e.response.text[:200]}"}
    except Exception as e:
        return {"ok": False, "error": str(e)}


async def list_projects() -> list[dict]:
    """
    List all accessible Jira projects.
    Returns list of {"key": "ENG", "name": "Engineering", "id": "10001"}
    """
    data = await _get("/project/search", params={"maxResults": 50})
    return [
        {
            "key": p["key"],
            "name": p["name"],
            "id": p["id"],
            "type": p.get("projectTypeKey", "software"),
        }
        for p in data.get("values", [])
    ]


async def list_issue_types(project_key: str) -> list[dict]:
    """
    List issue types available for a specific project.
    Returns list of {"id": "10001", "name": "Bug", "subtask": False}
    """
    data = await _get(f"/project/{project_key}")
    issue_types = data.get("issueTypes", [])
    return [
        {
            "id": it["id"],
            "name": it["name"],
            "subtask": it.get("subtask", False),
        }
        for it in issue_types
        if not it.get("subtask", False)  # Exclude subtask types
    ]


async def get_issue(issue_key: str) -> dict:
    """
    Get a single issue by key (e.g. "ENG-42").
    Returns simplified issue dict.
    """
    data = await _get(f"/issue/{issue_key}")
    fields = data.get("fields", {})
    return {
        "key": data["key"],
        "id": data["id"],
        "summary": fields.get("summary", ""),
        "status": fields.get("status", {}).get("name", "Unknown"),
        "priority": fields.get("priority", {}).get("name", "Medium"),
        "assignee": (fields.get("assignee") or {}).get("displayName", "Unassigned"),
        "issue_type": fields.get("issuetype", {}).get("name", "Task"),
        "url": f"{JIRA_BASE_URL}/browse/{data['key']}",
        "created": fields.get("created", ""),
        "updated": fields.get("updated", ""),
    }


async def create_issue(
    project_key: str,
    summary: str,
    description: str,
    issue_type: str = "Task",
    priority: str = "Medium",
    labels: list[str] = None,
    assignee_account_id: str = None,
) -> dict:
    """
    Create a new Jira issue.

    Args:
        project_key:        Project key (e.g. "ENG")
        summary:            Issue title (max ~250 chars)
        description:        Full description in Atlassian Document Format (ADF)
        issue_type:         "Task", "Bug", "Story", "Epic"
        priority:           "Highest", "High", "Medium", "Low", "Lowest"
        labels:             List of label strings (no spaces allowed in labels)
        assignee_account_id: Jira account ID to assign to (optional)

    Returns:
        {"key": "ENG-42", "id": "10042", "url": "https://..."}
    """
    fields: dict = {
        "project": {"key": project_key},
        "summary": summary[:255],  # Jira hard limit
        "description": _text_to_adf(description),
        "issuetype": {"name": issue_type},
        "priority": {"name": priority},
    }

    if labels:
        # Jira labels cannot have spaces — replace with hyphens
        fields["labels"] = [l.replace(" ", "-") for l in labels]

    if assignee_account_id:
        fields["assignee"] = {"accountId": assignee_account_id}

    body = {"fields": fields}

    try:
        data = await _post("/issue", body)
        issue_key = data["key"]
        return {
            "ok": True,
            "key": issue_key,
            "id": data["id"],
            "url": f"{JIRA_BASE_URL}/browse/{issue_key}",
        }
    except httpx.HTTPStatusError as e:
        error_body = {}
        try:
            error_body = e.response.json()
        except Exception:
            pass
        errors = error_body.get("errors", {})
        messages = error_body.get("errorMessages", [])
        return {
            "ok": False,
            "error": f"HTTP {e.response.status_code}",
            "details": errors or messages or e.response.text[:300],
        }
    except Exception as e:
        return {"ok": False, "error": str(e)}


async def search_issues(jql: str, max_results: int = 10) -> list[dict]:
    """
    Search issues using JQL (Jira Query Language).
    Example JQL: 'project = ENG AND labels = thirdeye AND status != Done'

    Returns list of simplified issue dicts.
    """
    data = await _get("/search/jql", params={
        "jql": jql,
        "maxResults": max_results,
        "fields": "summary,status,priority,assignee,issuetype,labels,created",
    })
    results = []
    for issue in data.get("issues", []):
        fields = issue.get("fields", {})
        results.append({
            "key": issue["key"],
            "summary": fields.get("summary", ""),
            "status": fields.get("status", {}).get("name", "Unknown"),
            "priority": fields.get("priority", {}).get("name", "Medium"),
            "assignee": (fields.get("assignee") or {}).get("displayName", "Unassigned"),
            "issue_type": fields.get("issuetype", {}).get("name", "Task"),
            "labels": fields.get("labels", []),
            "url": f"{JIRA_BASE_URL}/browse/{issue['key']}",
        })
    return results


async def search_users(query: str, max_results: int = 10) -> list[dict]:
    """
    Search Jira users by display name or email fragment.
    Returns list of {"account_id", "display_name", "email", "active"}.
    """
    try:
        data = await _get("/user/search", params={"query": query, "maxResults": max_results})
        return [
            {
                "account_id": u.get("accountId", ""),
                "display_name": u.get("displayName", ""),
                "email": u.get("emailAddress", ""),
                "active": u.get("active", True),
            }
            for u in data
            if u.get("active", True)
        ]
    except Exception as e:
        logger.warning(f"User search failed for '{query}': {e}")
        return []


async def assign_issue(issue_key: str, account_id: str) -> dict:
    """
    Assign a Jira issue to a user by their Jira account ID.
    Returns {"ok": True} on success or {"ok": False, "error": "..."}.
    """
    try:
        await _put(f"/issue/{issue_key}/assignee", {"accountId": account_id})
        return {"ok": True}
    except httpx.HTTPStatusError as e:
        return {"ok": False, "error": f"HTTP {e.response.status_code}: {e.response.text[:200]}"}
    except Exception as e:
        return {"ok": False, "error": str(e)}


async def add_comment(issue_key: str, comment: str) -> dict:
    """Add a plain-text comment to an existing issue."""
    try:
        data = await _post(f"/issue/{issue_key}/comment", {
            "body": _text_to_adf(comment)
        })
        return {"ok": True, "id": data.get("id")}
    except Exception as e:
        return {"ok": False, "error": str(e)}


# ─── ADF helper ──────────────────────────────────────────────────────────────

def _text_to_adf(text: str) -> dict:
    """
    Convert plain text to Atlassian Document Format (ADF).
    Jira Cloud requires ADF for description/comment fields (not plain strings).
    Splits on double newlines to create separate paragraphs.
    """
    paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
    if not paragraphs:
        paragraphs = [text.strip() or "(no description)"]

    content = []
    for para in paragraphs:
        # Handle bullet lines within a paragraph (lines starting with - or *)
        lines = para.split("\n")
        bullet_items = [l.lstrip("-* ").strip() for l in lines if l.strip().startswith(("-", "*", "•"))]
        non_bullets = [l for l in lines if not l.strip().startswith(("-", "*", "•"))]

        if non_bullets:
            content.append({
                "type": "paragraph",
                "content": [{"type": "text", "text": " ".join(non_bullets)}],
            })

        if bullet_items:
            content.append({
                "type": "bulletList",
                "content": [
                    {
                        "type": "listItem",
                        "content": [
                            {
                                "type": "paragraph",
                                "content": [{"type": "text", "text": item}],
                            }
                        ],
                    }
                    for item in bullet_items
                ],
            })

    return {
        "type": "doc",
        "version": 1,
        "content": content or [
            {"type": "paragraph", "content": [{"type": "text", "text": "(no description)"}]}
        ],
    }

