"""S3: Wykonywanie zadań przez narzędzia (CLI + API).

SCENARIUSZ: Narzędzie (aider/claude-code) wykonuje zadanie z ticketu.

FLOW:
  1. Dispatch ticket → tool
  2. Prompt zbudowany z: ticket.task + projekt context + code2llm
  3. Narzędzie uruchomione w katalogu projektu
  4. Logi zbierane w real-time
  5. Wynik → komentarz w tickecie + status update
"""
from __future__ import annotations

import pytest


class TestToolDispatch:
    """S3.1: Zlecanie zadania narzędziu."""

    def test_dispatch_creates_job(self, client, ticket_id):
        """Dispatch tworzy job w kolejce.

        MUSI:
        - Job ma: id, ticket_id, tool, status=queued
        - Prompt wygenerowany (nie pusty)

        NIE MOŻE:
        - Dispatch na nieistniejący ticket → 404
        - Dispatch na nieistniejące narzędzie → 400/404
        - Job bez promptu
        """
        r = client.post("/dashboard/tools/dispatch", json={
            "ticket_id": ticket_id,
            "tool": "aider",
        })
        assert r.status_code in (200, 201, 202)
        data = r.json()
        job = data.get("job", data)
        assert job.get("ticket_id") == ticket_id
        assert job.get("status") in ("queued", "running")

    def test_dispatch_unknown_tool_returns_error(self, client, ticket_id):
        """Nieznane narzędzie → error.

        NIE MOŻE:
        - Zwrócić 200
        - Stworzyć job
        """
        r = client.post("/dashboard/tools/dispatch", json={
            "ticket_id": ticket_id,
            "tool": "nieistniejace-narzedzie",
        })
        assert r.status_code >= 400

    def test_dispatch_nonexistent_ticket_returns_error(self, client):
        """Nieistniejący ticket → error."""
        r = client.post("/dashboard/tools/dispatch", json={
            "ticket_id": "T-000000",
            "tool": "aider",
        })
        assert r.status_code >= 400


class TestPromptGeneration:
    """S3.2: Generowanie promptu z ticketu."""

    def test_preview_prompt_contains_task(self, client, ticket_id):
        """Preview promptu.

        MUSI:
        - Zawierać task z ticketu
        - Zawierać ticket ID
        - Zawierać instrukcje per type (bug → "regression test")

        NIE MOŻE:
        - Być pusty
        - Zawierać API keys
        """
        r = client.post("/dashboard/tools/prompt/preview", json={
            "ticket_id": ticket_id,
            "tool": "aider",
        })
        assert r.status_code == 200
        prompt = r.json().get("prompt", "")
        assert len(prompt) > 20
        assert "api_key" not in prompt.lower()
        assert "sk-" not in prompt

    def test_preview_unknown_tool_returns_404(self, client, ticket_id):
        """Preview z nieznanym narzędziem → 404."""
        r = client.post("/dashboard/tools/prompt/preview", json={
            "ticket_id": ticket_id,
            "tool": "fake-tool-xyz",
        })
        assert r.status_code == 404


class TestJobExecution:
    """S3.3: Faktyczne wykonanie joba (z mock adapterem)."""

    def test_successful_job_updates_ticket(self, client, ticket_id, mock_adapter_success):
        """Udane wykonanie → ticket done + komentarz.

        MUSI:
        - Job status → completed/done
        - Ticket status → done
        - Komentarz z wynikiem

        NIE MOŻE:
        - Ticket zostać w in_progress
        - Brak komentarza
        """
        pass  # Requires async test with executor loop running

    def test_failed_job_updates_ticket_with_error(self, client, ticket_id, mock_adapter_failure):
        """Nieudane wykonanie → ticket failed + komentarz z błędem.

        MUSI:
        - Job status → failed
        - Ticket status → failed (lub error)
        - Komentarz: "aider ❌ failed — <error message>"
        - Error w observability ring buffer

        NIE MOŻE:
        - Ticket zostać w running
        - Error zniknąć bez śladu
        """
        pass  # Requires async test with executor loop running

    def test_timeout_job_is_marked_as_failed(self, client, ticket_id, mock_adapter_timeout):
        """Timeout → failed z informacją o timeout.

        MUSI:
        - Job status → failed
        - Error zawiera "timeout"
        """
        pass  # Requires async test with executor loop running


class TestToolLogs:
    """S3.4: Logi narzędzia przy wykonywaniu."""

    def test_queue_endpoint_returns_status(self, client):
        """Queue endpoint zwraca stan.

        MUSI:
        - GET /dashboard/tools/queue zwraca queue_size, total_jobs
        - by_status dict
        """
        r = client.get("/dashboard/tools/queue")
        assert r.status_code == 200
        data = r.json()
        assert "queue_size" in data or "total_jobs" in data

    def test_jobs_list_endpoint(self, client):
        """Lista jobów.

        MUSI:
        - GET /dashboard/tools/queue/jobs → lista
        - count w odpowiedzi
        """
        r = client.get("/dashboard/tools/queue/jobs")
        assert r.status_code == 200
        data = r.json()
        assert "jobs" in data
        assert isinstance(data["jobs"], list)

    def test_nonexistent_job_returns_404(self, client):
        """Nieistniejący job → 404."""
        r = client.get("/dashboard/tools/queue/jobs/FAKE-JOB-ID")
        assert r.status_code == 404
