import sys
import uuid

import pytest

from vorta.keyring.abc import VortaKeyring
from vorta.utils import (
    find_best_unit_for_sizes,
    get_path_datasize,
    is_system_tray_available,
    normalize_path,
    pretty_bytes,
    sort_sizes,
)


def test_keyring():
    UNICODE_PW = 'kjalsdfüadsfäadsfß'
    REPO = f'ssh://asdf123@vorta-test-repo.{uuid.uuid4()}.com/./repo'  # Random repo URL

    keyring = VortaKeyring.get_keyring()
    keyring.set_password('vorta-repo', REPO, UNICODE_PW)
    assert keyring.get_password("vorta-repo", REPO) == UNICODE_PW


@pytest.mark.parametrize(
    "input_sizes, expected_sorted",
    [
        # Basic ordering
        (["1.0 GB", "2.0 MB", "3.0 KB"], ["3.0 KB", "2.0 MB", "1.0 GB"]),
        # Multiple same units
        (["3.0 GB", "2.0 GB", "1.0 GB"], ["1.0 GB", "2.0 GB", "3.0 GB"]),
        # Multiple different units
        (["2.0 MB", "3.0 GB", "1.0 KB", "5.0 GB"], ["1.0 KB", "2.0 MB", "3.0 GB", "5.0 GB"]),
        # Larger to smaller units
        (["1.0 YB", "1.0 ZB", "1.0 EB", "1.0 PB"], ["1.0 PB", "1.0 EB", "1.0 ZB", "1.0 YB"]),
        # Skipping non-numeric sizes
        (["2x MB", "3.0 KB", "apple GB", "1.0 GB"], ["3.0 KB", "1.0 GB"]),
        # Skipping invalid suffix
        (["1.0 XX", "5.0 YY", "9.0 ZZ", "1.0 MB"], ["1.0 MB"]),
        # Floats with decimals
        (["2.5 GB", "2.3 GB", "1.1 MB"], ["1.1 MB", "2.3 GB", "2.5 GB"]),
        # Checking the same sizes across different units
        (["1.0 MB", "1000.0 KB"], ["1000.0 KB", "1.0 MB"]),
        # Handle empty lists
        ([], []),
    ],
)
def test_sort_sizes(input_sizes, expected_sorted):
    assert sort_sizes(input_sizes) == expected_sorted


@pytest.mark.parametrize(
    "precision, expected_unit",
    [
        (0, 1),  # return units as "1" (represents KB), min=100KB
        (1, 2),  # return units as "2" (represents MB), min=0.1MB
        (2, 2),  # still returns KB, since 0.1MB < min=0.001 GB to allow for GB to be best_unit
    ],
)
def test_best_unit_for_sizes_precision(precision, expected_unit):
    MB = 1000000
    sizes = [int(0.1 * MB), 100 * MB, 2000 * MB]
    best_unit = find_best_unit_for_sizes(sizes, metric=True, precision=precision)
    assert best_unit == expected_unit


@pytest.mark.parametrize(
    "sizes, expected_unit",
    [
        ([], 0),  # no sizes given but should still return "0" (represents bytes) as best representation
        ([102], 0),  # non-metric size 102 < 0.1KB (102 < 0.1 * 1024), so it will return 0 instead of 1
        ([103], 1),  # non-metric size 103 > 0.1KB (103 < 0.1 * 1024), so it will return 1
    ],
)
def test_best_unit_for_sizes_nonmetric(sizes, expected_unit):
    best_unit = find_best_unit_for_sizes(sizes, metric=False, precision=1)
    assert best_unit == expected_unit


@pytest.mark.parametrize(
    "size, metric, precision, fixed_unit, expected_output",
    [
        (10**5, True, 1, 2, "0.1 MB"),  # 100KB, metric, precision 1, fixed unit "2" (MB)
        (10**6, True, 0, 2, "1 MB"),  # 1MB, metric, precision 0, fixed unit "2" (MB)
        (10**6, True, 1, 2, "1.0 MB"),  # 1MB, metric, precision 1, fixed unit "2" (MB)
        (1024 * 1024, False, 1, 2, "1.0 MiB"),  # 1MiB, nonmetric, precision 1, fixed unit "2" (MiB)
    ],
)
def test_pretty_bytes_fixed_units(size, metric, precision, fixed_unit, expected_output):
    """
    Test pretty bytes when specifying a fixed unit of measurement
    """
    output = pretty_bytes(size, metric=metric, precision=precision, fixed_unit=fixed_unit)
    assert output == expected_output


@pytest.mark.parametrize(
    "size, metric, expected_output",
    [
        (10**6, True, "1.0 MB"),  # 1MB, metric
        (10**24, True, "1.0 YB"),  # 1YB, metric
        (10**30, True, "1000000.0 YB"),  # test huge number, metric
        (1024 * 1024, False, "1.0 MiB"),  # 1MiB, nonmetric
        (2**40 * 2**40, False, "1.0 YiB"),  # 1YiB, nonmetric
    ],
)
def test_pretty_bytes_nonfixed_units(size, metric, expected_output):
    # test pretty bytes when NOT specifying a fixed unit of measurement
    output = pretty_bytes(size, metric=metric, precision=1)
    assert output == expected_output


def test_normalize_path():
    """
    Test that path is normalized for macOS, but does nothing for other platforms.
    """
    input_path = '/Users/username/caf\u00e9/file.txt'
    expected_output = '/Users/username/café/file.txt'

    actual_output = normalize_path(input_path)

    if sys.platform == 'darwin':
        assert actual_output == expected_output
    else:
        assert actual_output == input_path


def test_get_path_datasize(tmpdir):
    """
    Test that get_path_datasize() works correctly when passed excluded patterns.
    """
    # Create a temporary directory for testing
    test_dir = tmpdir.mkdir("test_dir")
    test_file = test_dir.join("test_file.txt")
    test_file.write("Hello, World!")

    # Create a subdirectory with a file to exclude
    excluded_dir = test_dir.mkdir("excluded_dir")
    excluded_file = excluded_dir.join("excluded_file.txt")
    excluded_file.write("Excluded file, should not be checked.")

    exclude_patterns = [f"{excluded_dir}"]

    # Test when the path is a directory
    data_size, files_count = get_path_datasize(str(test_dir), exclude_patterns)
    assert data_size == len("Hello, World!")
    assert files_count == 1

    # Test when the path is a file
    data_size, files_count = get_path_datasize(str(test_file), exclude_patterns)
    assert data_size == len("Hello, World!")
    assert files_count == 1

    # Test when the path is a directory with an excluded file
    data_size, files_count = get_path_datasize(str(excluded_dir), exclude_patterns)
    assert data_size == 0
    assert files_count == 0


def test_is_system_tray_available(mocker):
    """
    Sanity check to ensure proper behavior
    """
    mocker.patch('PyQt6.QtWidgets.QSystemTrayIcon.isSystemTrayAvailable', return_value=False)
    assert is_system_tray_available() is False
    mocker.patch('PyQt6.QtWidgets.QSystemTrayIcon.isSystemTrayAvailable', return_value=True)
    assert is_system_tray_available() is True
