import timeit

import pytest
from django.core.exceptions import ImproperlyConfigured
from django.test import override_settings as override_django_settings
from django.test.utils import override_settings
from pytest_mock import MockerFixture

from collectfasta.management.commands.collectstatic import Command
from collectfasta.tests.conftest import StrategyFixture
from collectfasta.tests.conftest import aws_backends_only
from collectfasta.tests.conftest import cloud_backends_only
from collectfasta.tests.conftest import exclude_two_pass
from collectfasta.tests.conftest import live_test
from collectfasta.tests.conftest import speed_test
from collectfasta.tests.conftest import two_pass_only
from collectfasta.tests.utils import clean_static_dir
from collectfasta.tests.utils import create_static_file
from collectfasta.tests.utils import create_two_referenced_static_files
from collectfasta.tests.utils import make_100_files
from collectfasta.tests.utils import override_setting
from collectfasta.tests.utils import override_storage_attr

from .utils import call_collectstatic


@live_test
def test_basics(strategy: StrategyFixture) -> None:
    clean_static_dir()
    create_two_referenced_static_files()
    assert (
        f"{strategy.expected_copied_files(2)} static files copied."
        in call_collectstatic()
    )
    # file state should now be cached
    assert "0 static files copied." in call_collectstatic()


@live_test
def test_only_copies_new(strategy: StrategyFixture) -> None:
    clean_static_dir()
    create_two_referenced_static_files()
    assert (
        f"{strategy.expected_copied_files(2)} static files copied."
        in call_collectstatic()
    )
    create_two_referenced_static_files()
    # Since the files were already created and are expected to be cached/not copied again,
    # we expect 0 new files to be copied.
    assert (
        f"{strategy.expected_copied_files(2)} static files copied."
        in call_collectstatic()
    )


@live_test
@override_setting("threads", 5)
def test_threads(strategy: StrategyFixture) -> None:
    clean_static_dir()
    create_two_referenced_static_files()
    assert (
        f"{strategy.expected_copied_files(2)} static files copied."
        in call_collectstatic()
    )
    # file state should now be cached
    assert "0 static files copied." in call_collectstatic()


@cloud_backends_only
@speed_test
def test_basics_cloud_speed(strategy: StrategyFixture) -> None:
    clean_static_dir()
    make_100_files()
    assert (
        f"{strategy.expected_copied_files(100)} static files copied."
        in call_collectstatic()
    )

    def collectstatic_one():
        assert (
            f"{strategy.expected_copied_files(2)} static files copied."
            in call_collectstatic()
        )

    create_two_referenced_static_files()
    ittook = timeit.timeit(collectstatic_one, number=1)
    print(f"it took {ittook} seconds")


@cloud_backends_only
@speed_test
@override_settings(
    INSTALLED_APPS=["django.contrib.staticfiles"], COLLECTFASTA_STRATEGY=None
)
def test_no_collectfasta_cloud_speed(strategy: StrategyFixture) -> None:
    clean_static_dir()
    make_100_files()
    assert "100 static files copied" in call_collectstatic()

    def collectstatic_one():
        assert "2 static files copied" in call_collectstatic()

    create_two_referenced_static_files()
    ittook = timeit.timeit(collectstatic_one, number=1)
    print(f"it took {ittook} seconds")


@exclude_two_pass
def test_dry_run(strategy: StrategyFixture) -> None:
    clean_static_dir()
    create_static_file()
    result = call_collectstatic(dry_run=True)
    assert "1 static file copied." in result
    assert "Pretending to copy" in result
    result = call_collectstatic(dry_run=True)
    assert "1 static file copied." in result
    assert "Pretending to copy" in result
    assert "Pretending to delete" in result


@two_pass_only
def test_dry_run_two_pass(strategy: StrategyFixture) -> None:
    clean_static_dir()
    create_static_file()
    result = call_collectstatic(dry_run=True)
    assert "0 static files copied." in result
    assert "Pretending to copy" in result
    result = call_collectstatic(dry_run=True)
    assert "0 static files copied." in result
    assert "Pretending to copy" in result
    assert "Pretending to delete" in result


@aws_backends_only
@live_test
@override_storage_attr("gzip", True)
@override_setting("aws_is_gzipped", True)
def test_aws_is_gzipped(strategy: StrategyFixture) -> None:
    clean_static_dir()
    create_two_referenced_static_files()
    assert (
        f"{strategy.expected_copied_files(2)} static files copied."
        in call_collectstatic()
    )

    # file state should now be cached
    assert "0 static files copied." in call_collectstatic()


@override_django_settings(STORAGES={}, COLLECTFASTA_STRATEGY=None)
def test_raises_for_no_configured_strategy() -> None:
    with pytest.raises(ImproperlyConfigured):
        Command._load_strategy()


@live_test
def test_calls_post_copy_hook(strategy: StrategyFixture, mocker: MockerFixture) -> None:
    post_copy_hook = mocker.patch(
        "collectfasta.strategies.base.Strategy.post_copy_hook", autospec=True
    )
    clean_static_dir()
    (path_one, path_two) = create_two_referenced_static_files()
    cmd = Command()
    cmd.run_from_argv(["manage.py", "collectstatic", "--noinput"])
    post_copy_hook.assert_has_calls(
        [
            mocker.call(mocker.ANY, path_one.name, path_one.name, mocker.ANY),
            mocker.call(
                mocker.ANY,
                f"{path_one.name.replace('.html','')}_folder/{path_two.name}",
                f"{path_one.name.replace('.html','')}_folder/{path_two.name}",
                mocker.ANY,
            ),
        ],
        any_order=True,
    )


@live_test
def test_calls_on_skip_hook(strategy: StrategyFixture, mocker: MockerFixture) -> None:
    on_skip_hook = mocker.patch(
        "collectfasta.strategies.base.Strategy.on_skip_hook", autospec=True
    )
    clean_static_dir()
    (path_one, path_two) = create_two_referenced_static_files()
    cmd = Command()
    cmd.run_from_argv(["manage.py", "collectstatic", "--noinput"])
    on_skip_hook.assert_not_called()
    cmd.run_from_argv(["manage.py", "collectstatic", "--noinput"])
    on_skip_hook.assert_has_calls(
        [
            mocker.call(mocker.ANY, path_one.name, path_one.name, mocker.ANY),
            mocker.call(
                mocker.ANY,
                f"{path_one.name.replace('.html','')}_folder/{path_two.name}",
                f"{path_one.name.replace('.html','')}_folder/{path_two.name}",
                mocker.ANY,
            ),
        ],
        any_order=True,
    )
