import os
from dataclasses import dataclass
from pathlib import Path
from tempfile import TemporaryDirectory
from zipfile import ZipFile

import pytest
from fastapi.testclient import TestClient

from mealie.schema.group.group_migration import SupportedMigrations
from mealie.schema.reports.reports import ReportEntryOut
from tests import data as test_data
from tests.utils import api_routes
from tests.utils.assertion_helpers import assert_derserialize
from tests.utils.fixture_schemas import TestUser


@dataclass
class MigrationTestData:
    typ: SupportedMigrations
    archive: Path


test_cases = [
    MigrationTestData(typ=SupportedMigrations.nextcloud, archive=test_data.migrations_nextcloud),
    MigrationTestData(typ=SupportedMigrations.paprika, archive=test_data.migrations_paprika),
    MigrationTestData(typ=SupportedMigrations.chowdown, archive=test_data.migrations_chowdown),
    MigrationTestData(typ=SupportedMigrations.copymethat, archive=test_data.migrations_copymethat),
    MigrationTestData(typ=SupportedMigrations.mealie_alpha, archive=test_data.migrations_mealie),
    MigrationTestData(typ=SupportedMigrations.tandoor, archive=test_data.migrations_tandoor),
    MigrationTestData(typ=SupportedMigrations.plantoeat, archive=test_data.migrations_plantoeat),
    MigrationTestData(typ=SupportedMigrations.myrecipebox, archive=test_data.migrations_myrecipebox),
]

test_ids = [
    "nextcloud_archive",
    "paprika_archive",
    "chowdown_archive",
    "copymethat_archive",
    "mealie_alpha_archive",
    "tandoor_archive",
    "plantoeat_archive",
    "myrecipebox_csv",
]


@pytest.mark.parametrize("mig", test_cases, ids=test_ids)
def test_recipe_migration(api_client: TestClient, unique_user: TestUser, mig: MigrationTestData) -> None:
    payload = {
        "migration_type": mig.typ.value,
    }

    file_payload = {
        "archive": mig.archive.read_bytes(),
    }

    response = api_client.post(
        api_routes.groups_migrations, data=payload, files=file_payload, headers=unique_user.token
    )

    assert response.status_code == 200

    report_id = response.json()["id"]

    # Validate Results
    response = api_client.get(api_routes.groups_reports_item_id(report_id), headers=unique_user.token)

    assert response.status_code == 200
    response_json = response.json()
    assert response_json["entries"]

    for item in response_json["entries"]:
        assert item["success"]

    # Validate Create Event
    params = {"orderBy": "created_at", "orderDirection": "desc"}
    response = api_client.get(api_routes.recipes, params=params, headers=unique_user.token)
    query_data = assert_derserialize(response)
    assert len(query_data["items"])

    recipe_id = query_data["items"][0]["id"]
    params = {"queryFilter": f"recipe_id={recipe_id}"}

    response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=unique_user.token)
    query_data = assert_derserialize(response)
    events = query_data["items"]
    assert len(events)


def test_bad_mealie_alpha_data_is_ignored(api_client: TestClient, unique_user: TestUser):
    with TemporaryDirectory() as tmpdir:
        with ZipFile(test_data.migrations_mealie) as zf:
            zf.extractall(tmpdir)

        invalid_recipe_dir = os.path.join(tmpdir, "mealie_2021-Dec-08", "recipes", "invalid-recipe")
        os.makedirs(invalid_recipe_dir, exist_ok=True)
        invalid_json_path = os.path.join(invalid_recipe_dir, "invalid-recipe.json")
        try:
            with open(invalid_json_path, "w"):
                pass  # write nothing to the file, which is invalid JSON
        except Exception:
            raise Exception(os.listdir(tmpdir))

        modified_test_data = os.path.join(tmpdir, "modified-test-data.zip")
        with ZipFile(modified_test_data, "w") as zf:
            for root, _, files in os.walk(tmpdir):
                for file in files:
                    file_path = os.path.join(root, file)
                    zf.write(file_path, arcname=os.path.relpath(file_path, tmpdir))

        payload = {
            "migration_type": SupportedMigrations.mealie_alpha.value,
        }

        file_payload = {
            "archive": Path(modified_test_data).read_bytes(),
        }

        response = api_client.post(
            api_routes.groups_migrations, data=payload, files=file_payload, headers=unique_user.token
        )

        assert response.status_code == 200
        report_id = response.json()["id"]

    # Validate Results
    response = api_client.get(api_routes.groups_reports_item_id(report_id), headers=unique_user.token)

    assert response.status_code == 200
    response_json = response.json()
    assert response_json["entries"]

    failed_item = None
    failed_item_count = 0
    for item in response_json["entries"]:
        if item["success"]:
            continue

        failed_item = item
        failed_item_count += 1

    assert failed_item
    assert failed_item_count == 1

    report_entry = ReportEntryOut.model_validate(failed_item)
    assert report_entry.message == "Failed to import invalid-recipe.json"
    assert report_entry.exception == "JSONDecodeError: Expecting value: line 1 column 1 (char 0)"
