"""Domain models.

These are immutable (frozen) dataclasses. They represent the *public* API of
this client.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any


@dataclass(frozen=True, slots=True)
class Pipeline:
    id: str
    name: str

    @staticmethod
    def from_node(node: dict[str, Any]) -> "Pipeline":
        return Pipeline(id=str(node["id"]), name=str(node["name"]))


@dataclass(frozen=True, slots=True)
class SprintInfo:
    id: str
    name: str
    state: str | None = None
    start_at: str | None = None
    end_at: str | None = None

    @staticmethod
    def from_node(node: dict[str, Any]) -> "SprintInfo":
        return SprintInfo(
            id=str(node["id"]),
            name=str(node["name"]),
            state=str(node.get("state")) if node.get("state") is not None else None,
            start_at=str(node.get("startAt")) if node.get("startAt") is not None else None,
            end_at=str(node.get("endAt")) if node.get("endAt") is not None else None,
        )


@dataclass(frozen=True, slots=True)
class SprintWorkloadSummary:
    sprint: SprintInfo
    total_points: float
    issue_count: int
    missing_estimate_count: int
    closed_issue_count: int
    closed_points: float


@dataclass(frozen=True, slots=True)
class IssueInfo:
    id: str
    title: str
    number: int
    state: str
    html_url: str

    @staticmethod
    def from_node(node: dict[str, Any]) -> "IssueInfo":
        return IssueInfo(
            id=str(node["id"]),
            title=str(node.get("title", "")),
            number=int(node.get("number", 0)),
            state=str(node.get("state", "")),
            html_url=str(node.get("htmlUrl", "")),
        )


@dataclass(frozen=True, slots=True)
class SprintIssue:
    """A sprint issue with optional GitHub-derived fields (labels/assignees)."""

    id: str
    number: int
    title: str
    state: str | None
    html_url: str | None
    estimate: float | None
    repository_gh_id: int | None
    pipeline_name: str | None
    labels: tuple[str, ...]
    assignees: tuple[str, ...]

    @staticmethod
    def from_node(node: dict[str, Any]) -> "SprintIssue":
        estimate_val: float | None = None
        estimate = node.get("estimate")
        if isinstance(estimate, dict) and estimate.get("value") is not None:
            try:
                estimate_val = float(estimate.get("value"))
            except (TypeError, ValueError):
                estimate_val = None

        repo_gh_id: int | None = None
        if node.get("repositoryGhId") is not None:
            try:
                repo_gh_id = int(node.get("repositoryGhId"))
            except (TypeError, ValueError):
                repo_gh_id = None

        pipeline_name: str | None = None
        pipeline_issue = node.get("pipelineIssue")
        if isinstance(pipeline_issue, dict):
            pipeline = pipeline_issue.get("pipeline")
            if isinstance(pipeline, dict) and pipeline.get("name") is not None:
                pipeline_name = str(pipeline.get("name"))

        labels: list[str] = []
        labels_conn = node.get("labels")
        if isinstance(labels_conn, dict):
            label_nodes = labels_conn.get("nodes")
            if isinstance(label_nodes, list):
                for ln in label_nodes:
                    if isinstance(ln, dict) and ln.get("name") is not None:
                        labels.append(str(ln.get("name")))

        assignees: list[str] = []
        assignees_conn = node.get("assignees")
        if isinstance(assignees_conn, dict):
            assignee_nodes = assignees_conn.get("nodes")
            if isinstance(assignee_nodes, list):
                for an in assignee_nodes:
                    if isinstance(an, dict) and an.get("login") is not None:
                        assignees.append(str(an.get("login")))

        return SprintIssue(
            id=str(node["id"]),
            number=int(node.get("number", 0)),
            title=str(node.get("title", "")),
            state=str(node.get("state")) if node.get("state") is not None else None,
            html_url=str(node.get("htmlUrl")) if node.get("htmlUrl") is not None else None,
            estimate=estimate_val,
            repository_gh_id=repo_gh_id,
            pipeline_name=pipeline_name,
            labels=tuple(labels),
            assignees=tuple(assignees),
        )
