"""
Nom du script : issues.py
Chemin : /gitlab-bridge/app/schemas/issues.py
Description : Schémas Pydantic liés à la création d'issues GitLab.
Options éventuelles : Aucune.
Exemples d'utilisation : Endpoint POST /api/v1/issues/create.
Prérequis : Python 3.11+, Pydantic 2.x.
Auteur : Sylvain SCATTOLINI
Date de création / modification : 2026-03-25
Version : 1.1
"""

from __future__ import annotations

from pydantic import BaseModel, ConfigDict, Field, field_validator

from app.schemas.common import AuthContext
from app.schemas.milestones import MilestoneCandidateItem, MilestoneItem

class IssueAssistantCreateRequest(BaseModel):

    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255)
    title: str = Field(..., min_length=1, max_length=255)
    description: str = Field(default="", max_length=20000)
    labels: list[str] = Field(default_factory=list, max_length=50)
    # Milestone optionnel : hint en langage naturel ou id direct
    milestone_hint: str = Field(default="", max_length=255)
    resolved_milestone_id: int = Field(default=0, ge=0)
    root_hint: str = Field(default="", max_length=255)
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    
class IssueCreateRequest(BaseModel):
    """Payload de création d'issue."""

    model_config = ConfigDict(extra='forbid', str_strip_whitespace=True)

    auth: AuthContext
    project_path: str = Field(min_length=1, max_length=255)
    title: str = Field(min_length=1, max_length=255)
    description: str = Field(min_length=1, max_length=20000)
    labels: list[str] = Field(default_factory=list, max_length=50)

    @field_validator('labels')
    @classmethod
    def validate_labels(cls, value: list[str]) -> list[str]:
        cleaned: list[str] = []
        for label in value:
            label = label.strip()
            if label and label not in cleaned:
                cleaned.append(label)
        return cleaned


class IssuePayload(BaseModel):
    """Issue créée par GitLab."""

    id: int
    iid: int
    title: str
    web_url: str


class IssueCreateResponse(BaseModel):
    """Réponse de création d'issue."""

    success: bool = True
    issue: IssuePayload

class IssueListItem(BaseModel):
    model_config = ConfigDict(extra="forbid")

    id: int
    iid: int
    title: str
    state: str
    web_url: str

class IssueAssistantListRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255)
    root_hint: str = Field(default="", max_length=255)
    state: str = Field(default="all", pattern="^(all|opened|closed)$")
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    limit: int = Field(default=100, ge=1, le=200)

class IssueAssistantListResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    issues: list[IssueListItem] = Field(default_factory=list)
    candidate_projects: list[dict] = Field(default_factory=list)

class IssueAssistantCloseRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    issue_hint: str = Field(..., min_length=1, max_length=255)
    project_hint: str = Field(default="", max_length=255)
    root_hint: str = Field(default="", max_length=255)
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    resolved_issue_iid: int = Field(default=0, ge=0)
    limit: int = Field(default=20, ge=1, le=100)

class IssueCandidateItem(BaseModel):
    model_config = ConfigDict(extra="forbid")

    id: int
    iid: int
    title: str
    state: str
    web_url: str
    score: int

class IssueAssistantCloseResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    issue: IssueListItem | None = None
    candidate_projects: list[dict] = Field(default_factory=list)
    candidate_issues: list[IssueCandidateItem] = Field(default_factory=list)

class IssueAssistantReopenRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    issue_hint: str = Field(..., min_length=1, max_length=255)
    project_hint: str = Field(default="", max_length=255)
    root_hint: str = Field(default="", max_length=255)
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    resolved_issue_iid: int = Field(default=0, ge=0)
    limit: int = Field(default=20, ge=1, le=100)

class IssueAssistantReopenResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    issue: IssueListItem | None = None
    candidate_projects: list[dict] = Field(default_factory=list)
    candidate_issues: list[IssueCandidateItem] = Field(default_factory=list)

class IssueDuplicateItem(BaseModel):
    model_config = ConfigDict(extra="forbid")

    id: int
    iid: int
    title: str
    state: str
    web_url: str
    score: int

class IssueAssistantCreateResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    issue: IssuePayload | None = None
    candidate_projects: list[dict] = Field(default_factory=list)
    duplicate_issues: list[IssueDuplicateItem] = Field(default_factory=list)

class IssueAssistantListLabelsRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255)
    root_hint: str = Field(default="", max_length=255)
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    limit: int = Field(default=100, ge=1, le=200)


class LabelItem(BaseModel):
    model_config = ConfigDict(extra="forbid")

    id: int | None = None
    name: str
    color: str | None = None
    description: str | None = None
    text_color: str | None = None


class IssueAssistantListLabelsResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    labels: list[LabelItem] = Field(default_factory=list)
    candidate_projects: list[dict] = Field(default_factory=list)


class IssueAssistantAddLabelRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    issue_hint: str = Field(..., min_length=1, max_length=255)
    label_name: str = Field(..., min_length=1, max_length=255)
    project_hint: str = Field(default="", max_length=255)
    root_hint: str = Field(default="", max_length=255)
    create_label_if_missing: bool = Field(default=True)
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    resolved_issue_iid: int = Field(default=0, ge=0)
    limit: int = Field(default=20, ge=1, le=100)


class IssueAssistantAddLabelResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    issue: IssueListItem | None = None
    labels: list[str] = Field(default_factory=list)
    candidate_projects: list[dict] = Field(default_factory=list)
    candidate_issues: list[IssueCandidateItem] = Field(default_factory=list)  

class BulkIssueResultItem(BaseModel):
    model_config = ConfigDict(extra="forbid")

    issue_hint: str
    status: str
    message: str
    issue: IssueListItem | None = None
    labels: list[str] = Field(default_factory=list)
    candidate_issues: list[IssueCandidateItem] = Field(default_factory=list)


class IssueAssistantAddLabelBulkRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255)
    issue_hints: list[str] = Field(..., min_length=1, max_length=200)
    label_name: str = Field(..., min_length=1, max_length=255)
    root_hint: str = Field(default="", max_length=255)
    create_label_if_missing: bool = Field(default=True)
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    limit: int = Field(default=20, ge=1, le=100)


class IssueAssistantAddLabelBulkResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    label_name: str
    results: list[BulkIssueResultItem] = Field(default_factory=list)
    candidate_projects: list[dict] = Field(default_factory=list)  

class IssueAssistantAddLabelAllRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255)
    label_name: str = Field(..., min_length=1, max_length=255)
    root_hint: str = Field(default="", max_length=255)
    state: str = Field(default="all", pattern="^(all|opened|closed)$")
    create_label_if_missing: bool = Field(default=True)
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    limit: int = Field(default=200, ge=1, le=500)


class IssueAssistantAddLabelAllResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    label_name: str
    total_issues: int = 0
    updated_count: int = 0
    results: list[BulkIssueResultItem] = Field(default_factory=list)
    candidate_projects: list[dict] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Schémas pour la création en lot d'issues (assistant-batch-create)
# ---------------------------------------------------------------------------

class BatchIssueInput(BaseModel):
    """Une issue à créer dans le lot."""
    model_config = ConfigDict(extra="forbid")

    title: str = Field(..., min_length=1, max_length=255)
    description: str = Field(default="", max_length=20000)
    labels: list[str] = Field(default_factory=list, max_length=50)


class BatchIssueResult(BaseModel):
    """Résultat de création pour une issue du lot."""
    model_config = ConfigDict(extra="forbid")

    title: str
    status: str                   # "created" | "error"
    message: str
    issue: IssuePayload | None = None
    milestone_assigned: bool = False


class IssueAssistantBatchCreateRequest(BaseModel):
    """
    Crée N issues en un seul appel, avec assignment optionnel à une milestone.

    Flux en deux temps (comme les autres endpoints assistant) :
    - confirm=False : résout le projet + la milestone, retourne les candidats si ambiguïté
    - confirm=True  : résolution confirmée, crée toutes les issues et assigne la milestone
    """
    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255,
                              description="Nom/path partiel du projet GitLab cible.")
    issues: list[BatchIssueInput] = Field(..., min_length=1, max_length=50,
                                          description="Liste des issues à créer (max 50).")
    milestone_hint: str = Field(default="", max_length=255,
                                description="Nom partiel de la milestone à assigner (optionnel).")
    root_hint: str = Field(default="", max_length=255,
                           description="Préfixe de namespace pour filtrer la recherche.")
    confirm: bool = Field(default=False,
                          description="True = créer toutes les issues avec les resolved_* fournis.")
    resolved_project_path: str = Field(default="", max_length=255,
                                       description="Path exact du projet (obligatoire si confirm=True).")
    resolved_milestone_id: int = Field(default=0, ge=0,
                                       description="ID de la milestone (0 = aucune milestone).")


class IssueAssistantBatchCreateResponse(BaseModel):
    """Résultat de la création en lot."""
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    milestone: MilestoneItem | None = None
    results: list[BatchIssueResult] = Field(default_factory=list)
    created_count: int = 0
    error_count: int = 0
    candidate_projects: list[dict] = Field(default_factory=list)
    candidate_milestones: list[MilestoneCandidateItem] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Schémas pour la mise à jour d'une issue (assistant-update)
# ---------------------------------------------------------------------------

class IssueAssistantUpdateRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255)
    issue_hint: str = Field(..., min_length=1, max_length=255)
    root_hint: str = Field(default="", max_length=255)
    new_title: str = Field(default="", max_length=255,
                           description="Nouveau titre. Laissez vide pour ne pas modifier.")
    new_description: str = Field(default="", max_length=20000,
                                 description="Nouvelle description. Laissez vide pour ne pas modifier.")
    add_labels: list[str] = Field(default_factory=list, max_length=50,
                                  description="Labels à ajouter.")
    remove_labels: list[str] = Field(default_factory=list, max_length=50,
                                     description="Labels à retirer.")
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    resolved_issue_iid: int = Field(default=0, ge=0)
    limit: int = Field(default=20, ge=1, le=100)


class IssueAssistantUpdateResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    issue: IssueListItem | None = None
    candidate_projects: list[dict] = Field(default_factory=list)
    candidate_issues: list[IssueCandidateItem] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Schémas pour commenter une issue (assistant-comment)
# ---------------------------------------------------------------------------

class IssueAssistantCommentRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255)
    issue_hint: str = Field(..., min_length=1, max_length=255)
    body: str = Field(..., min_length=1, max_length=50000,
                      description="Corps du commentaire à ajouter à l'issue.")
    root_hint: str = Field(default="", max_length=255)
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    resolved_issue_iid: int = Field(default=0, ge=0)
    limit: int = Field(default=20, ge=1, le=100)


class IssueAssistantCommentResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    issue: IssueListItem | None = None
    comment_id: int | None = None
    comment_url: str | None = None
    candidate_projects: list[dict] = Field(default_factory=list)
    candidate_issues: list[IssueCandidateItem] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Schémas pour assigner un utilisateur à une issue (assistant-assign-user)
# ---------------------------------------------------------------------------

class UserCandidateItem(BaseModel):
    model_config = ConfigDict(extra="forbid")

    id: int
    username: str
    name: str
    state: str
    score: int


class IssueAssistantAssignUserRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255)
    issue_hint: str = Field(..., min_length=1, max_length=255)
    user_hint: str = Field(..., min_length=1, max_length=255,
                           description="Nom ou username GitLab de la personne à assigner.")
    root_hint: str = Field(default="", max_length=255)
    unassign: bool = Field(default=False,
                           description="Si True, retire tous les assignés de l'issue.")
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    resolved_issue_iid: int = Field(default=0, ge=0)
    resolved_user_id: int = Field(default=0, ge=0)
    limit: int = Field(default=20, ge=1, le=100)


class IssueAssistantAssignUserResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    issue: IssueListItem | None = None
    assigned_user: dict | None = None
    candidate_projects: list[dict] = Field(default_factory=list)
    candidate_issues: list[IssueCandidateItem] = Field(default_factory=list)
    candidate_users: list[UserCandidateItem] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Schémas pour fixer une date d'échéance (assistant-set-due-date)
# ---------------------------------------------------------------------------

class IssueAssistantSetDueDateRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255)
    issue_hint: str = Field(..., min_length=1, max_length=255)
    due_date: str = Field(default="", max_length=10,
                          description="Date d'échéance au format YYYY-MM-DD. Vide pour supprimer.")
    root_hint: str = Field(default="", max_length=255)
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    resolved_issue_iid: int = Field(default=0, ge=0)
    limit: int = Field(default=20, ge=1, le=100)


class IssueAssistantSetDueDateResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    issue: IssueListItem | None = None
    due_date: str | None = None
    candidate_projects: list[dict] = Field(default_factory=list)
    candidate_issues: list[IssueCandidateItem] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Schémas pour la fermeture en lot d'issues (assistant-bulk-close)
# ---------------------------------------------------------------------------

class BulkCloseResult(BaseModel):
    model_config = ConfigDict(extra="forbid")

    issue_hint: str
    status: str    # "closed" | "not_found" | "ambiguous" | "error"
    message: str
    issue: IssueListItem | None = None


class IssueAssistantBulkCloseRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    project_hint: str = Field(..., min_length=1, max_length=255)
    issue_hints: list[str] = Field(..., min_length=1, max_length=100,
                                   description="Titres partiels des issues à fermer.")
    root_hint: str = Field(default="", max_length=255)
    confirm: bool = Field(default=False)
    resolved_project_path: str = Field(default="", max_length=255)
    limit: int = Field(default=20, ge=1, le=100)


class IssueAssistantBulkCloseResponse(BaseModel):
    model_config = ConfigDict(extra="forbid")

    success: bool = True
    status: str
    message: str
    project: dict | None = None
    results: list[BulkCloseResult] = Field(default_factory=list)
    closed_count: int = 0
    not_found_count: int = 0
    error_count: int = 0
    candidate_projects: list[dict] = Field(default_factory=list)