"""Jira Server/DC source connector — REST API v2 with Bearer PAT auth.

Supports incremental sync via local cache: on first run downloads all issues,
on subsequent runs only fetches issues updated since the last sync.
"""

from __future__ import annotations

import hashlib
import logging
from dataclasses import dataclass, field
from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING, Any

import httpx
from rich.progress import Progress, TaskID

from redhat_oracle.core.config import OracleConfig
from redhat_oracle.core.vectordb.chunker import Chunk

if TYPE_CHECKING:
    from redhat_oracle.core.vectordb.store import VectorStore

logger = logging.getLogger(__name__)


@dataclass
class JiraComment:
    """A single Jira issue comment."""

    comment_id: str
    author: str
    body: str
    created: str  # ISO 8601
    issue_key: str


@dataclass
class JiraIssue:
    """A Jira issue with optional comments."""

    key: str
    summary: str
    description: str
    status: str
    priority: str
    assignee: str
    reporter: str
    labels: list[str] = field(default_factory=list)
    created: str = ""  # ISO 8601
    updated: str = ""  # ISO 8601
    url: str = ""
    comments: list[JiraComment] = field(default_factory=list)

    def to_dict(self) -> dict:
        """Serialize to dict for JSON caching."""
        return {
            "key": self.key,
            "summary": self.summary,
            "description": self.description,
            "status": self.status,
            "priority": self.priority,
            "assignee": self.assignee,
            "reporter": self.reporter,
            "labels": self.labels,
            "created": self.created,
            "updated": self.updated,
            "url": self.url,
            "comments": [
                {
                    "comment_id": c.comment_id,
                    "author": c.author,
                    "body": c.body,
                    "created": c.created,
                    "issue_key": c.issue_key,
                }
                for c in self.comments
            ],
        }

    @classmethod
    def from_dict(cls, d: dict) -> JiraIssue:
        """Deserialize from cached dict."""
        issue = cls(
            key=d["key"],
            summary=d.get("summary", ""),
            description=d.get("description", ""),
            status=d.get("status", ""),
            priority=d.get("priority", ""),
            assignee=d.get("assignee", ""),
            reporter=d.get("reporter", ""),
            labels=d.get("labels", []),
            created=d.get("created", ""),
            updated=d.get("updated", ""),
            url=d.get("url", ""),
        )
        for c in d.get("comments", []):
            issue.comments.append(JiraComment(
                comment_id=c.get("comment_id", ""),
                author=c.get("author", ""),
                body=c.get("body", ""),
                created=c.get("created", ""),
                issue_key=c.get("issue_key", issue.key),
            ))
        return issue


def _generate_chunk_id(prefix: str, content: str) -> str:
    """Generate a deterministic chunk ID."""
    content_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()[:12]
    return f"{prefix}::{content_hash}"


def _parse_issue(issue_data: dict, base_url: str, include_comments: bool) -> JiraIssue:
    """Parse a Jira API issue response into a JiraIssue object."""
    fields = issue_data.get("fields", {})
    issue = JiraIssue(
        key=issue_data["key"],
        summary=fields.get("summary", ""),
        description=fields.get("description") or "",
        status=(fields.get("status") or {}).get("name", ""),
        priority=(fields.get("priority") or {}).get("name", ""),
        assignee=(fields.get("assignee") or {}).get("displayName", ""),
        reporter=(fields.get("reporter") or {}).get("displayName", ""),
        labels=fields.get("labels", []),
        created=fields.get("created", ""),
        updated=fields.get("updated", ""),
        url=f"{base_url}/browse/{issue_data['key']}",
    )

    if include_comments:
        for c in (fields.get("comment", {}).get("comments", [])):
            issue.comments.append(JiraComment(
                comment_id=c.get("id", ""),
                author=(c.get("author") or {}).get("displayName", ""),
                body=c.get("body", ""),
                created=c.get("created", ""),
                issue_key=issue.key,
            ))

    return issue


async def sync_jira(
    config: OracleConfig,
    store: VectorStore | None = None,
    progress: Progress | None = None,
) -> dict[str, Any]:
    """Fetch Jira issues with incremental sync via local cache.

    First run: downloads all issues within history_days.
    Subsequent runs: only fetches issues updated since last sync.
    When store is provided, upserts chunks after each project for
    incremental persistence.

    Returns:
        Dict with stats: issues, chunks.
    """
    from redhat_oracle.core.cache import SyncCache

    jira_config = config.sources.jira
    token = jira_config.token

    if not token:
        logger.warning("No Jira token configured, skipping Jira sync")
        return {"issues": 0, "chunks": 0}

    base_url = jira_config.url.rstrip("/")
    headers = {
        "Authorization": f"Bearer {token}",
        "Accept": "application/json",
    }

    cache = SyncCache(config)
    total_issues = 0
    total_chunks = 0

    task_id: TaskID | None = None
    if progress:
        task_id = progress.add_task(
            "[cyan]Fetching Jira issues", total=len(jira_config.projects)
        )

    async with httpx.AsyncClient(timeout=30.0, headers=headers) as client:
        for project in jira_config.projects:
            if progress and task_id is not None:
                progress.update(task_id, description=f"[cyan]Jira: {project}")

            # Determine date range — incremental if we have a last sync
            last_sync = cache.get_jira_last_sync(project)
            if last_sync:
                # Incremental: only issues updated since last sync
                since_str = last_sync[:10]  # "2026-02-10T..." → "2026-02-10"
                logger.info("Jira %s: incremental sync since %s", project, since_str)
            else:
                # Full: all issues within history_days
                since = datetime.now(UTC) - timedelta(days=jira_config.history_days)
                since_str = since.strftime("%Y-%m-%d")
                logger.info("Jira %s: full sync (last %d days)", project, jira_config.history_days)

            jql = f'project = "{project}" AND updated >= "{since_str}" ORDER BY updated DESC'
            start_at = 0
            fetched: list[JiraIssue] = []

            while True:
                params = {
                    "jql": jql,
                    "startAt": start_at,
                    "maxResults": jira_config.max_results,
                    "fields": "summary,description,status,priority,assignee,"
                    "reporter,labels,created,updated,comment",
                }

                resp = await client.get(
                    f"{base_url}/rest/api/2/search", params=params
                )
                resp.raise_for_status()
                data = resp.json()

                issues_data = data.get("issues", [])
                if not issues_data:
                    break

                for issue_data in issues_data:
                    issue = _parse_issue(issue_data, base_url, jira_config.include_comments)
                    fetched.append(issue)

                start_at += len(issues_data)
                if start_at >= data.get("total", 0):
                    break

            logger.info("Jira %s: fetched %d issues from API", project, len(fetched))

            # Merge into cache
            fetched_dicts = [i.to_dict() for i in fetched]
            all_cached = cache.merge_jira_issues(project, fetched_dicts)

            # Update sync timestamp
            now = datetime.now(UTC).isoformat()
            cache.set_jira_last_sync(project, now)

            # Convert all cached issues to JiraIssue objects and upsert
            project_issues = [JiraIssue.from_dict(d) for d in all_cached]
            project_chunks = chunks_from_jira_issues(project_issues)
            if store and project_chunks:
                store.upsert_chunks(project_chunks)
            total_issues += len(project_issues)
            total_chunks += len(project_chunks)

            logger.info("Jira %s: %d issues, %d chunks indexed",
                        project, len(project_issues), len(project_chunks))

            if progress and task_id is not None:
                progress.advance(task_id)

    logger.info("Jira total: %d issues, %d chunks", total_issues, total_chunks)
    return {"issues": total_issues, "chunks": total_chunks}


def chunks_from_jira_issues(issues: list[JiraIssue]) -> list[Chunk]:
    """Convert Jira issues and their comments into Chunk objects.

    One chunk per issue (title + description) plus one chunk per comment.
    """
    chunks: list[Chunk] = []

    for issue in issues:
        # Issue chunk: summary + description
        issue_text = f"{issue.key}: {issue.summary}\n\n{issue.description}"
        chunk_id = _generate_chunk_id(f"jira/{issue.key}", issue_text)
        chunks.append(
            Chunk(
                chunk_id=chunk_id,
                text=issue_text,
                source="jira",
                org=issue.key.split("-")[0] if "-" in issue.key else "",
                repo="",
                path=issue.key,
                content_type="jira_issue",
                author=issue.reporter,
                timestamp=issue.created,
                url=issue.url,
                jira_issue_id=issue.key,
                status=issue.status,
                priority=issue.priority,
                labels=issue.labels if issue.labels else None,
            )
        )

        # Comment chunks
        for comment in issue.comments:
            comment_text = f"Comment on {issue.key} by {comment.author}:\n{comment.body}"
            cid = _generate_chunk_id(
                f"jira/{issue.key}/comment/{comment.comment_id}", comment_text
            )
            chunks.append(
                Chunk(
                    chunk_id=cid,
                    text=comment_text,
                    source="jira",
                    org=issue.key.split("-")[0] if "-" in issue.key else "",
                    repo="",
                    path=f"{issue.key}/comment/{comment.comment_id}",
                    content_type="jira_comment",
                    author=comment.author,
                    timestamp=comment.created,
                    url=issue.url,
                    jira_issue_id=issue.key,
                    status=issue.status,
                    priority=issue.priority,
                    labels=issue.labels if issue.labels else None,
                )
            )

    return chunks
