"""Tests for lib/jobs.py — Job object pattern."""

import time

import pytest

from recoil.core.jobs import Job


class TestJobCreate:
    def test_create_sets_defaults(self):
        job = Job.create(model="gemini-3-pro-image-preview")
        assert job.status == "submitted"
        assert job.model == "gemini-3-pro-image-preview"
        assert len(job.job_id) == 12
        assert job.result is None
        assert job._client is None

    def test_create_with_client(self):
        mock_client = object()
        job = Job.create(model="kling-v3", client=mock_client)
        assert job._client is mock_client

    def test_unique_ids(self):
        ids = {Job.create(model="x").job_id for _ in range(100)}
        assert len(ids) == 100


class TestJobLifecycle:
    def test_mark_complete(self):
        from recoil.execution.api_client import GenerationResult
        job = Job.create(model="test")
        result = GenerationResult(success=True, image_data=b"img", model="test", cost=0.134)
        job.mark_complete(result)

        assert job.status == "complete"
        assert job.result is result
        assert job.completed_at is not None

    def test_mark_failed(self):
        job = Job.create(model="test")
        job.mark_failed("Rate limited")

        assert job.status == "failed"
        assert job.error == "Rate limited"
        assert job.completed_at is not None

    def test_wait_complete_returns_result(self):
        from recoil.execution.api_client import GenerationResult
        job = Job.create(model="test")
        result = GenerationResult(success=True, model="test")
        job.mark_complete(result)

        assert job.wait() is result

    def test_wait_failed_raises(self):
        job = Job.create(model="test")
        job.mark_failed("Error")

        with pytest.raises(RuntimeError, match="failed"):
            job.wait()

    def test_wait_detached_raises(self):
        job = Job.create(model="test")
        # status is "submitted" but no client
        with pytest.raises(RuntimeError, match="detached"):
            job.wait()


class TestJobSerialization:
    def test_to_dict_basic(self):
        job = Job.create(model="gemini-3-pro-image-preview")
        d = job.to_dict()

        assert d["job_id"] == job.job_id
        assert d["model"] == "gemini-3-pro-image-preview"
        assert d["status"] == "submitted"
        assert "submitted_at" in d
        assert "error" not in d  # No error on fresh job

    def test_to_dict_with_result(self):
        from recoil.execution.api_client import GenerationResult
        job = Job.create(model="test")
        result = GenerationResult(success=True, model="test", cost=0.134)
        job.mark_complete(result)
        d = job.to_dict()

        assert d["status"] == "complete"
        assert d["result_success"] is True
        assert d["result_cost"] == 0.134

    def test_to_dict_with_error(self):
        job = Job.create(model="test")
        job.mark_failed("Timeout")
        d = job.to_dict()

        assert d["error"] == "Timeout"
        assert d["status"] == "failed"

    def test_from_dict_roundtrip(self):
        job = Job.create(model="kling-v3")
        job.mark_failed("API down")
        d = job.to_dict()

        restored = Job.from_dict(d)
        assert restored.job_id == job.job_id
        assert restored.model == "kling-v3"
        assert restored.status == "failed"
        assert restored.error == "API down"
        assert restored._client is None  # Detached

    def test_from_dict_preserves_timestamps(self):
        job = Job.create(model="test")
        d = job.to_dict()
        restored = Job.from_dict(d)
        assert restored.submitted_at == job.submitted_at
