import tempfile
import uuid
from pathlib import Path

import pytest
from requests import HTTPError

from arkindex.exceptions import ErrorResponse
from arkindex_worker.models import Artifact
from tests import FIXTURES_DIR
from tests.test_elements_worker import BASE_API_CALLS

TASK_ID = uuid.UUID("cafecafe-cafe-cafe-cafe-cafecafecafe")


@pytest.mark.parametrize(
    ("payload", "error"),
    [
        # Task ID
        (
            {"task_id": None},
            "task_id shouldn't be null and should be an UUID",
        ),
        (
            {"task_id": "12341234-1234-1234-1234-123412341234"},
            "task_id shouldn't be null and should be an UUID",
        ),
    ],
)
def test_list_artifacts_wrong_param_task_id(mock_dataset_worker, payload, error):
    with pytest.raises(AssertionError, match=error):
        mock_dataset_worker.list_artifacts(**payload)


def test_list_artifacts_api_error(responses, mock_dataset_worker):
    responses.add(
        responses.GET,
        f"http://testserver/api/v1/task/{TASK_ID}/artifacts/",
        status=418,
    )

    with pytest.raises(ErrorResponse):
        mock_dataset_worker.list_artifacts(task_id=TASK_ID)

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("GET", f"http://testserver/api/v1/task/{TASK_ID}/artifacts/")
    ]


def test_list_artifacts(
    responses,
    mock_dataset_worker,
):
    expected_results = [
        {
            "id": "artifact_1",
            "path": "dataset_id.tar.zst",
            "size": 42,
            "content_type": "application/zstd",
            "s3_put_url": None,
            "created": "2000-01-01T00:00:00Z",
            "updated": "2000-01-01T00:00:00Z",
        },
        {
            "id": "artifact_2",
            "path": "logs.log",
            "size": 42,
            "content_type": "text/plain",
            "s3_put_url": None,
            "created": "2000-01-01T00:00:00Z",
            "updated": "2000-01-01T00:00:00Z",
        },
    ]
    responses.add(
        responses.GET,
        f"http://testserver/api/v1/task/{TASK_ID}/artifacts/",
        status=200,
        json=expected_results,
    )

    for idx, artifact in enumerate(mock_dataset_worker.list_artifacts(task_id=TASK_ID)):
        assert isinstance(artifact, Artifact)
        assert artifact == expected_results[idx]

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("GET", f"http://testserver/api/v1/task/{TASK_ID}/artifacts/"),
    ]


@pytest.mark.parametrize(
    ("payload", "error"),
    [
        # Task ID
        (
            {"task_id": None},
            "task_id shouldn't be null and should be an UUID",
        ),
        (
            {"task_id": "12341234-1234-1234-1234-123412341234"},
            "task_id shouldn't be null and should be an UUID",
        ),
    ],
)
def test_download_artifact_wrong_param_task_id(
    mock_dataset_worker, default_artifact, payload, error
):
    api_payload = {
        "task_id": TASK_ID,
        "artifact": default_artifact,
        **payload,
    }

    with pytest.raises(AssertionError, match=error):
        mock_dataset_worker.download_artifact(**api_payload)


@pytest.mark.parametrize(
    ("payload", "error"),
    [
        # Artifact
        (
            {"artifact": None},
            "artifact shouldn't be null and should be an Artifact",
        ),
        (
            {"artifact": "not artifact type"},
            "artifact shouldn't be null and should be an Artifact",
        ),
    ],
)
def test_download_artifact_wrong_param_artifact(
    mock_dataset_worker, default_artifact, payload, error
):
    api_payload = {
        "task_id": TASK_ID,
        "artifact": default_artifact,
        **payload,
    }

    with pytest.raises(AssertionError, match=error):
        mock_dataset_worker.download_artifact(**api_payload)


def test_download_artifact_api_error(responses, mock_dataset_worker, default_artifact):
    responses.add(
        responses.GET,
        f"http://testserver/api/v1/task/{TASK_ID}/artifact/dataset_id.tar.zst",
        status=418,
    )

    with pytest.raises(ErrorResponse):
        mock_dataset_worker.download_artifact(
            task_id=TASK_ID, artifact=default_artifact
        )

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("GET", f"http://testserver/api/v1/task/{TASK_ID}/artifact/dataset_id.tar.zst")
    ]


def test_download_artifact(
    responses,
    mock_dataset_worker,
    default_artifact,
):
    archive_path = (
        FIXTURES_DIR
        / "extract_parent_archives"
        / "first_parent"
        / "arkindex_data.tar.zst"
    )
    responses.add(
        responses.GET,
        f"http://testserver/api/v1/task/{TASK_ID}/artifact/dataset_id.tar.zst",
        status=200,
        body=archive_path.read_bytes(),
        content_type="application/zstd",
    )

    assert (
        mock_dataset_worker.download_artifact(
            task_id=TASK_ID, artifact=default_artifact
        ).read()
        == archive_path.read_bytes()
    )

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("GET", f"http://testserver/api/v1/task/{TASK_ID}/artifact/dataset_id.tar.zst"),
    ]


@pytest.mark.parametrize(
    ("payload", "error"),
    [
        # Path
        (
            {"path": None},
            "path shouldn't be null, should be a Path and should exist",
        ),
        (
            {"path": "not path type"},
            "path shouldn't be null, should be a Path and should exist",
        ),
        (
            {"path": Path("i_do_no_exist.oops")},
            "path shouldn't be null, should be a Path and should exist",
        ),
    ],
)
def test_upload_artifact_wrong_param_path(mock_dataset_worker, payload, error):
    with pytest.raises(AssertionError, match=error):
        mock_dataset_worker.upload_artifact(**payload)


@pytest.fixture
def tmp_file(mock_dataset_worker):
    with tempfile.NamedTemporaryFile(
        mode="w", suffix=".txt", dir=mock_dataset_worker.work_dir
    ) as file:
        file.write("Some content...")
        file.seek(0)

        yield Path(file.name)


def test_upload_artifact_api_error(responses, mock_dataset_worker, tmp_file):
    responses.add(
        responses.POST,
        "http://testserver/api/v1/task/my_task/artifacts/",
        status=418,
    )

    with pytest.raises(ErrorResponse):
        mock_dataset_worker.upload_artifact(path=tmp_file)

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [("POST", "http://testserver/api/v1/task/my_task/artifacts/")]


def test_upload_artifact_s3_upload_error(
    responses,
    mock_dataset_worker,
    tmp_file,
):
    responses.add(
        responses.POST,
        "http://testserver/api/v1/task/my_task/artifacts/",
        json={
            "id": "11111111-1111-1111-1111-111111111111",
            "path": tmp_file.name,
            "size": 15,
            "content_type": "text/plain",
            "s3_put_url": "http://example.com/oops.txt",
        },
    )
    responses.add(responses.PUT, "http://example.com/oops.txt", status=500)

    with pytest.raises(HTTPError):
        mock_dataset_worker.upload_artifact(path=tmp_file)

    assert len(responses.calls) == len(BASE_API_CALLS) + 2
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("POST", "http://testserver/api/v1/task/my_task/artifacts/"),
        ("PUT", "http://example.com/oops.txt"),
    ]


def test_upload_artifact(
    responses,
    mock_dataset_worker,
    tmp_file,
):
    responses.add(
        responses.POST,
        "http://testserver/api/v1/task/my_task/artifacts/",
        json={
            "id": "11111111-1111-1111-1111-111111111111",
            "path": tmp_file.name,
            "size": 15,
            "content_type": "text/plain",
            "s3_put_url": "http://example.com/test.txt",
        },
    )
    responses.add(responses.PUT, "http://example.com/test.txt")

    mock_dataset_worker.upload_artifact(path=tmp_file)

    assert len(responses.calls) == len(BASE_API_CALLS) + 2
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("POST", "http://testserver/api/v1/task/my_task/artifacts/"),
        ("PUT", "http://example.com/test.txt"),
    ]
