import datetime
from pathlib import Path
from unittest.mock import Mock

import pytest
from pytest_mock import MockerFixture

from game import Game
from game.persistence import SaveManager, set_dcs_save_game_directory


@pytest.fixture(autouse=True)
def mock_setup_last_save_file(mocker: MockerFixture) -> Mock:
    return mocker.patch("qt_ui.liberation_install.setup_last_save_file")


@pytest.fixture(autouse=True)
def mock_save_config(mocker: MockerFixture) -> Mock:
    return mocker.patch("qt_ui.liberation_install.save_config")


@pytest.fixture(autouse=True)
def setup_persistence_paths(tmp_path: Path) -> None:
    set_dcs_save_game_directory(tmp_path)


@pytest.fixture
def save_manager(game: Game) -> SaveManager:
    return game.save_manager


def test_new_savemanager_saves_to_autosave(save_manager: SaveManager) -> None:
    assert save_manager.default_save_location == save_manager.autosave_path


def test_savemanager_saves_to_last_save_location(save_manager: SaveManager) -> None:
    save_manager.player_save_location = Path("Saves/foo.liberation.zip")
    assert save_manager.default_save_location == save_manager.player_save_location


def test_saving_without_name_saves_to_autosave_path(save_manager: SaveManager) -> None:
    assert not save_manager.autosave_path.exists()
    save_manager.save_player()
    assert save_manager.autosave_path.exists()


def test_saving_with_name_updates_last_save_location(
    save_manager: SaveManager, tmp_path: Path
) -> None:
    save_path = tmp_path / "foo.liberation.zip"
    assert not save_path.exists()
    save_manager.save_player(override_destination=save_path)
    assert save_path.exists()
    assert save_manager.last_saved_bundle is not None
    assert save_manager.last_saved_bundle.bundle_path == save_path


def test_player_save_location(save_manager: SaveManager, tmp_path: Path) -> None:
    assert save_manager.player_save_location is None
    save_manager.save_last_turn()
    assert save_manager.player_save_location is None
    save_manager.save_start_of_turn()
    assert save_manager.player_save_location is None
    expect_location = tmp_path / "player.liberation.zip"
    save_manager.save_player(override_destination=expect_location)
    assert save_manager.player_save_location == expect_location


def test_saving_updates_preferences_with_save_location(
    save_manager: SaveManager, mock_setup_last_save_file: Mock, mock_save_config: Mock
) -> None:
    save_manager.save_player()
    mock_setup_last_save_file.assert_called_once_with(
        str(save_manager.default_save_location)
    )
    mock_save_config.assert_called_once()


def test_non_player_saves_do_not_update_preferences(
    save_manager: SaveManager, mock_setup_last_save_file: Mock
) -> None:
    save_manager.save_last_turn()
    mock_setup_last_save_file.assert_not_called()
    save_manager.save_start_of_turn()
    mock_setup_last_save_file.assert_not_called()


def test_load_game_loads_correct_data(save_manager: SaveManager) -> None:
    test_date = datetime.date.today()
    assert save_manager.game.date != test_date
    save_manager.game.date = test_date
    save_manager.save_player()
    game = SaveManager.load_player_save(save_manager.default_save_location)
    assert game.date == test_date


def METHOD_NAME() -> None:
    with pytest.raises(FileNotFoundError):
        SaveManager.load_player_save(Path("does not exist"))


def test_saving_after_autosave_copies_autosave_members(
    save_manager: SaveManager, tmp_path: Path
) -> None:
    save_manager.save_start_of_turn()

    save_path = tmp_path / "foo.liberation.zip"
    save_manager.save_player(override_destination=save_path)

    SaveManager.load_start_of_turn(save_path)


def test_failed_save_does_not_update_last_saved_path(
    save_manager: SaveManager, tmp_path: Path
) -> None:
    expect_date = datetime.date.today()
    save_manager.game.date = expect_date
    save_manager.save_player()
    assert save_manager.last_saved_bundle is not None
    expect_path = save_manager.last_saved_bundle.bundle_path

    # Add some non-pickleable member to the game to cause an error on save.
    def local_f() -> None:
        pass

    save_manager.game.garbage = local_f  # type: ignore
    with pytest.raises(AttributeError):
        save_manager.save_player(
            override_destination=tmp_path / "badsave.liberation.zip"
        )

    assert save_manager.last_saved_bundle.bundle_path == expect_path


def test_load_reads_correct_data(save_manager: SaveManager) -> None:
    last_turn_date = datetime.date.today() - datetime.timedelta(days=2)
    save_manager.game.date = last_turn_date
    save_manager.save_last_turn()

    start_of_turn_date = datetime.date.today() - datetime.timedelta(days=1)
    save_manager.game.date = start_of_turn_date
    save_manager.save_start_of_turn()

    player_date = datetime.date.today()
    save_manager.game.date = player_date
    save_manager.save_player()

    assert save_manager.last_saved_bundle is not None
    bundle_path = save_manager.last_saved_bundle.bundle_path
    assert SaveManager.load_last_turn(bundle_path).date == last_turn_date
    assert SaveManager.load_start_of_turn(bundle_path).date == start_of_turn_date
    assert SaveManager.load_player_save(bundle_path).date == player_date


def test_save_after_loading_foreign_save(
    save_manager: SaveManager, tmp_path: Path
) -> None:
    """Tests that we can save games that were copied from another machine.

    Regression test for https://github.com/dcs-liberation/dcs_liberation/issues/2756.
    """
    # To simulate the situation from the bug, we save a game to a directory, move it out
    # of that directory, delete the directory, then attempt to load and save the game.
    # It should save to the new location. If it tries to save to the old location, it
    # will fail because the directory does not exist.

    # Create the save on "the other machine"...
    bad_directory = tmp_path / "other-machine"
    bad_directory.mkdir()
    bad_save_path = bad_directory / "foo.liberation.zip"
    save_manager.save_player(override_destination=bad_save_path)

    good_save_path = tmp_path / "foo.liberation.zip"
    bad_save_path.rename(good_save_path)
    bad_directory.rmdir()

    game = SaveManager.load_player_save(good_save_path)
    assert game.save_manager.player_save_location == good_save_path
    game.save_manager.save_player()