"""
Azure DevOps REST API client (api-version=7.1).

Handles:
- Monthly iteration (classification node) — auto-creates if missing
- Monthly User Story ("Maintenance :: Month Year") — auto-creates if missing
- Task creation under the User Story, linked to Zammad ticket
- Idempotent: checks for existing task by ticket number before creating

Auth: HTTP Basic with PAT (Personal Access Token).
URL: Supports both hosted (dev.azure.com) and self-hosted (DEVOPS_URL).
"""

import base64
import httpx
from datetime import datetime
from .config import settings


def _auth() -> dict:
    """Basic auth headers for standard JSON requests."""
    token = base64.b64encode(f":{settings.devops_pat}".encode()).decode()
    return {
        "Authorization": f"Basic {token}",
        "Content-Type": "application/json",
    }


def _patch_headers() -> dict:
    """Basic auth headers for JSON Patch requests (work item create/update)."""
    token = base64.b64encode(f":{settings.devops_pat}".encode()).decode()
    return {
        "Authorization": f"Basic {token}",
        "Content-Type": "application/json-patch+json",
    }


def _root() -> str:
    """Base URL including org: https://host/org"""
    return f"{settings.devops_url.rstrip('/')}/{settings.devops_org}"


def _base() -> str:
    """Base URL including org + project."""
    return f"{_root()}/{settings.devops_project}"


def _team_base() -> str:
    """Base URL including org + project + team (for team-scoped settings)."""
    return f"{_root()}/{settings.devops_project}/{settings.devops_team}"


def iteration_name() -> str:
    """Current month iteration name, e.g. M_JUN_2026."""
    now = datetime.now()
    return f"M_{now.strftime('%b').upper()}_{now.year}"


def story_title() -> str:
    """Current month User Story title, e.g. 'Maintenance :: June 2026'."""
    now = datetime.now()
    return f"Maintenance :: {now.strftime('%B %Y')}"


async def get_or_create_iteration() -> str:
    """Ensure monthly iteration classification node exists and is assigned to the team.
    Returns full iteration path like 'ProjectName\\M_JUN_2026'."""
    name = iteration_name()
    api = "7.1"

    async with httpx.AsyncClient(timeout=20) as client:
        # Check if classification node exists
        r = await client.get(
            f"{_base()}/_apis/wit/classificationNodes/iterations/{name}?api-version={api}",
            headers=_auth(),
        )
        if r.status_code == 200:
            node = r.json()
        else:
            # Create classification node
            r2 = await client.post(
                f"{_base()}/_apis/wit/classificationNodes/iterations?api-version={api}",
                headers=_auth(),
                json={"name": name},
            )
            r2.raise_for_status()
            node = r2.json()

        # Assign to team (idempotent — DevOps ignores duplicate assignment)
        iter_id = node["id"]
        await client.post(
            f"{_team_base()}/_apis/work/teamsettings/iterations?api-version={api}",
            headers=_auth(),
            json={"id": iter_id},
        )

    return f"{settings.devops_project}\\{name}"


async def check_iteration_exists(name: str) -> bool:
    """Return True if iteration classification node exists (used by web UI validation)."""
    async with httpx.AsyncClient(timeout=20) as client:
        r = await client.get(
            f"{_base()}/_apis/wit/classificationNodes/iterations/{name}?api-version=7.1",
            headers=_auth(),
        )
        return r.status_code == 200


async def get_work_item(item_id: int) -> dict | None:
    """Fetch a work item by ID. Returns None if not found (404)."""
    async with httpx.AsyncClient(timeout=20) as client:
        r = await client.get(
            f"{_base()}/_apis/wit/workitems/{item_id}?api-version=7.1",
            headers=_auth(),
        )
        if r.status_code == 404:
            return None
        r.raise_for_status()
        return r.json()


async def find_existing_task(ticket_number: str) -> dict | None:
    """Search for an existing Task with this ticket number in the title.
    Returns {id, url} if found, None otherwise."""
    wiql = {
        "query": (
            "SELECT [System.Id] FROM WorkItems "
            "WHERE [System.TeamProject] = @project "
            "AND [System.WorkItemType] = 'Task' "
            f"AND [System.Title] CONTAINS '#{ticket_number} ' "
            "AND [System.State] <> 'Removed'"
        )
    }
    async with httpx.AsyncClient(timeout=20) as client:
        r = await client.post(
            f"{_base()}/_apis/wit/wiql?api-version=7.1",
            headers=_auth(),
            json=wiql,
        )
        r.raise_for_status()
        items = r.json().get("workItems", [])
        if not items:
            return None
        task_id = items[0]["id"]
        task_url = f"{_root()}/{settings.devops_project}/_workitems/edit/{task_id}"
        return {"id": task_id, "url": task_url}


async def find_user_story(title: str, iter_path: str) -> int | None:
    """Search for a User Story with exact title in the given iteration.
    Returns work item ID if found, None otherwise."""
    wiql = {
        "query": (
            "SELECT [System.Id] FROM WorkItems "
            "WHERE [System.TeamProject] = @project "
            f"AND [System.WorkItemType] = 'User Story' "
            f"AND [System.Title] = '{title}' "
            f"AND [System.IterationPath] = '{iter_path}' "
            "AND [System.State] <> 'Removed'"
        )
    }
    async with httpx.AsyncClient(timeout=20) as client:
        r = await client.post(
            f"{_base()}/_apis/wit/wiql?api-version=7.1",
            headers=_auth(),
            json=wiql,
        )
        r.raise_for_status()
        items = r.json().get("workItems", [])
        return items[0]["id"] if items else None


async def create_user_story(title: str, iter_path: str) -> int:
    """Create a new User Story work item. Returns its ID."""
    patch = [
        {"op": "add", "path": "/fields/System.Title",         "value": title},
        {"op": "add", "path": "/fields/System.IterationPath", "value": iter_path},
        {"op": "add", "path": "/fields/System.WorkItemType",  "value": "User Story"},
        {"op": "add", "path": "/fields/System.State",         "value": "Active"},
    ]
    async with httpx.AsyncClient(timeout=20) as client:
        r = await client.post(
            f"{_base()}/_apis/wit/workitems/$User Story?api-version=7.1",
            headers=_patch_headers(),
            json=patch,
        )
        r.raise_for_status()
        return r.json()["id"]


async def create_task(
    story_id: int,
    ticket_id: int,
    ticket_number: str,
    ticket_title: str,
    ticket_url: str,
    iter_path: str | None = None,
    activity: str = "Development",
    original_estimate: float = 0.5,
    remaining_work: float = 0.5,
    expert_estimate: float = 0.25,
    tags: str = "",
) -> dict:
    """Create a Task work item linked to a parent User Story.

    Title format: '#<ticket_number> <ticket_title>'
    Custom.ExpertEstimate maps to a custom field in this DevOps instance.
    Returns {id, url} of created task.
    """
    if iter_path is None:
        iter_path = await get_or_create_iteration()

    description = (
        f"<b>Zammad Ticket:</b> #{ticket_number}<br>"
        f"<b>Title:</b> {ticket_title}<br>"
        f'<b>Link:</b> <a href="{ticket_url}">{ticket_url}</a>'
    )

    patch = [
        {"op": "add", "path": "/fields/System.Title",
         "value": f"#{ticket_number} {ticket_title}"},
        {"op": "add", "path": "/fields/System.Description",                      "value": description},
        {"op": "add", "path": "/fields/System.IterationPath",                    "value": iter_path},
        {"op": "add", "path": "/fields/System.State",                            "value": "New"},
        # Link task as child of the monthly User Story
        {"op": "add", "path": "/relations/-", "value": {
            "rel": "System.LinkTypes.Hierarchy-Reverse",
            "url": f"{_root()}/_apis/wit/workitems/{story_id}",
        }},
    ]

    patch += [
        {"op": "add", "path": "/fields/Microsoft.VSTS.Common.Activity",             "value": activity},
        {"op": "add", "path": "/fields/Microsoft.VSTS.Scheduling.OriginalEstimate", "value": original_estimate},
        {"op": "add", "path": "/fields/Microsoft.VSTS.Scheduling.RemainingWork",    "value": remaining_work},
        {"op": "add", "path": "/fields/Custom.ExpertEstimate",                       "value": expert_estimate},
    ]
    if tags:
        patch.append({"op": "add", "path": "/fields/System.Tags", "value": tags})

    async with httpx.AsyncClient(timeout=20) as client:
        r = await client.post(
            f"{_base()}/_apis/wit/workitems/$Task?api-version=7.1",
            headers=_patch_headers(),
            json=patch,
        )
        r.raise_for_status()
        item = r.json()
        task_id = item["id"]
        task_url = f"{_root()}/{settings.devops_project}/_workitems/edit/{task_id}"
        return {"id": task_id, "url": task_url}


async def ensure_and_create(
    ticket_id: int,
    ticket_number: str,
    ticket_title: str,
    ticket_url: str,
    activity: str = "Development",
    tags: str = "",
) -> dict:
    """Idempotent task creation flow:
      1. Return existing task if found (no duplicate)
      2. Ensure monthly iteration exists
      3. Ensure monthly User Story exists
      4. Create and return new Task
    Returns {story_id, task_id, task_url, created: bool}."""
    existing = await find_existing_task(ticket_number)
    if existing:
        return {"story_id": None, "task_id": existing["id"], "task_url": existing["url"], "created": False}

    iter_path = await get_or_create_iteration()
    s_title   = story_title()

    story_id = await find_user_story(s_title, iter_path)
    if story_id is None:
        story_id = await create_user_story(s_title, iter_path)

    task = await create_task(
        story_id, ticket_id, ticket_number, ticket_title, ticket_url,
        iter_path=iter_path,
        activity=activity,
        tags=tags,
    )
    return {"story_id": story_id, "task_id": task["id"], "task_url": task["url"], "created": True}
