"""Workflow-oriented models for local state and reconciliation."""

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, model_validator

from .branch import BranchState
from .metabranch import MetabranchDefinition


def utc_now() -> str:
    """Return an ISO8601 UTC timestamp."""
    return datetime.now(timezone.utc).isoformat()


class RepoLock(BaseModel):
    """Repo-wide lock for mutating metabranch workflows."""

    owner: str
    workflow_id: str | None = None
    operation: str | None = None
    metabranch_name: str | None = None
    acquired_at: str = Field(default_factory=utc_now)

    model_config = ConfigDict(frozen=True, extra="ignore")

    @model_validator(mode="before")
    @classmethod
    def normalize_fields(cls, value: Any) -> Any:
        """Coerce optional repo lock fields into stable shapes."""
        if not isinstance(value, dict):
            return value
        normalized = dict(value)
        for field_name in ("workflow_id", "operation", "metabranch_name"):
            field_value = value.get(field_name)
            normalized[field_name] = (
                field_value if isinstance(field_value, str) else None
            )
        normalized["acquired_at"] = str(value.get("acquired_at", utc_now()))
        return normalized

    def age_seconds(self, now: datetime | None = None) -> int:
        """Return the age of the lock in whole seconds."""
        current = now or datetime.now(timezone.utc)
        acquired = datetime.fromisoformat(self.acquired_at)
        if acquired.tzinfo is None:
            acquired = acquired.replace(tzinfo=timezone.utc)
        return max(0, int((current - acquired).total_seconds()))


class RepoLockStatus(BaseModel):
    """Inspected state of the repo-wide metabranch lock."""

    lock: RepoLock
    is_stale: bool = False
    reason: str | None = None

    model_config = ConfigDict(frozen=True)


class ReconciliationPlanEntry(BaseModel):
    """A user-selected target branch for a metabranch-only commit."""

    action: str = "assign"
    target_branch: str | None = None
    commit_sha: str
    subject: str

    model_config = ConfigDict(frozen=True, extra="ignore")

    @model_validator(mode="after")
    def validate_shape(self) -> ReconciliationPlanEntry:
        """Keep assign/drop plan entries in a consistent shape."""
        if self.action not in {"assign", "drop"}:
            raise ValueError("Unsupported reconciliation action.")
        if self.action == "assign" and not self.target_branch:
            raise ValueError("Assign reconciliation entries require a target branch.")
        return self


class WorkflowJournal(BaseModel):
    """Journaled state for a mutating metabranch workflow."""

    workflow_id: str
    workflow_type: str
    metabranch_name: str
    status: str = "pending"
    current_step: str = "pending"
    args: dict[str, object] = Field(default_factory=dict)
    checkpoints: dict[str, str] = Field(default_factory=dict)
    created_at: str = Field(default_factory=utc_now)
    updated_at: str = Field(default_factory=utc_now)

    model_config = ConfigDict(frozen=True, extra="ignore")

    @model_validator(mode="before")
    @classmethod
    def normalize_fields(cls, value: Any) -> Any:
        """Coerce persisted journal fields into stable shapes."""
        if not isinstance(value, dict):
            return value
        normalized = dict(value)
        args = value.get("args", {})
        checkpoints = value.get("checkpoints", {})
        normalized["args"] = dict(args) if isinstance(args, dict) else {}
        normalized["checkpoints"] = (
            {str(key): str(item) for key, item in checkpoints.items()}
            if isinstance(checkpoints, dict)
            else {}
        )
        normalized["created_at"] = str(value.get("created_at", utc_now()))
        normalized["updated_at"] = str(value.get("updated_at", utc_now()))
        return normalized

    def with_updates(
        self,
        *,
        status: str | None = None,
        current_step: str | None = None,
        args: dict[str, object] | None = None,
        checkpoints: dict[str, str] | None = None,
    ) -> WorkflowJournal:
        """Return a copy of the journal with updated state."""
        return self.model_copy(
            update={
                "status": status or self.status,
                "current_step": current_step or self.current_step,
                "args": dict(self.args if args is None else args),
                "checkpoints": dict(
                    self.checkpoints if checkpoints is None else checkpoints
                ),
                "updated_at": utc_now(),
            }
        )


class StatusResult(BaseModel):
    """Structured status view for the current branch and workflow state."""

    branch_name: str
    is_metabranch: bool
    metabranch: MetabranchDefinition | None = None
    branch_states: tuple[BranchState, ...] = ()
    tracking_branch: str | None = None
    ahead: int | None = None
    behind: int | None = None
    remote_config_created_at: str | None = None
    remote_config_updated_at: str | None = None
    remote_head_updated_at: str | None = None
    active_workflow: WorkflowJournal | None = None
    repo_lock: RepoLockStatus | None = None
    local_active_metabranch: str | None = None
    notices: tuple[str, ...] = ()

    model_config = ConfigDict(frozen=True)

    @property
    def statuses(self) -> tuple[str, ...]:
        """Compatibility string view used by legacy tests and callers."""
        lines: list[str] = []
        if self.is_metabranch and self.metabranch is not None:
            lines.append(f"On branch {self.metabranch.name}")
            lines.extend(
                f"{branch_state.name}: {branch_state.state}"
                for branch_state in self.branch_states
            )
        else:
            lines.extend(self.notices)
        if self.active_workflow is not None:
            lines.append(
                "Active workflow: "
                f"{self.active_workflow.workflow_type} "
                f"({self.active_workflow.status} at {self.active_workflow.current_step})"
            )
        if self.repo_lock is not None:
            suffix = (
                f" (stale: {self.repo_lock.reason})"
                if self.repo_lock.is_stale and self.repo_lock.reason is not None
                else ""
            )
            lines.append(f"Repo lock: {self.repo_lock.lock.owner}{suffix}")
        return tuple(lines)
