import os
from datetime import datetime
from pathlib import Path
from unittest.mock import patch

from gallery import (
    build_gallery,
    calculate_directory_stats,
    convert_pdf_to_png,
    datetime_from_timestamp,
    format_file_size,
    needs_update,
    strftime_filter,
)


def test_format_file_size():
    assert format_file_size(0) == "0 B"
    assert format_file_size(1) == "1.0 B"
    assert format_file_size(1024) == "1.0 KB"
    assert format_file_size(1048576) == "1.0 MB"
    assert format_file_size(1073741824) == "1.0 GB"
    assert format_file_size(1099511627776) == "1.0 TB"


def test_format_file_size_edge_cases():
    assert format_file_size(1023) == "1023.0 B"
    assert format_file_size(1536) == "1.5 KB"
    assert format_file_size(2621440) == "2.5 MB"


def test_needs_update_missing_target(tmp_path):
    source = tmp_path / "source.txt"
    target = tmp_path / "target.txt"
    source.write_text("test content")

    assert needs_update(source, target) is True


def test_needs_update_target_newer(tmp_path):
    source = tmp_path / "source.txt"
    target = tmp_path / "target.txt"

    source.write_text("test")
    target.write_text("test")

    # Make target newer by modifying its timestamp
    import time

    time.sleep(0.1)
    target.touch()

    assert needs_update(source, target) is False


def test_needs_update_source_newer(tmp_path):
    source = tmp_path / "source.txt"
    target = tmp_path / "target.txt"

    target.write_text("test")
    import time

    time.sleep(0.1)
    source.write_text("test")

    # Force different modification times with 31+ second buffer
    target_time = target.stat().st_mtime
    source_time = target_time + 40  # 40 seconds newer (> 30 second buffer)
    os.utime(source, (source_time, source_time))

    assert needs_update(source, target) is True


@patch("gallery.utils.processing._convert_pdf_pymupdf")
def test_convert_pdf_to_png_success(mock_convert, tmp_path):
    from gallery import GalleryConfig

    pdf_path = tmp_path / "test.pdf"
    png_path = tmp_path / "test.png"
    pdf_path.write_text("fake pdf")

    config = GalleryConfig(tmp_path)
    convert_pdf_to_png(pdf_path, config)

    mock_convert.assert_called_once_with(pdf_path, png_path, config.png_dpi)


@patch("gallery.utils.processing._convert_pdf_pymupdf")
def test_convert_pdf_to_png_already_exists_newer(mock_convert, tmp_path):
    from gallery import GalleryConfig

    pdf_path = tmp_path / "test.pdf"
    png_path = tmp_path / "test.png"

    pdf_path.write_text("fake pdf")
    png_path.write_text("fake png")

    # Make PNG much newer than PDF using explicit time
    pdf_time = pdf_path.stat().st_mtime
    png_time = pdf_time + 100  # PNG is 100 seconds newer
    os.utime(png_path, (png_time, png_time))

    convert_pdf_to_png(pdf_path, GalleryConfig(tmp_path))

    # Should not convert since PNG is newer
    mock_convert.assert_not_called()


@patch("gallery.utils.processing._convert_pdf_pymupdf")
def test_convert_pdf_to_png_pdf_newer(mock_convert, tmp_path):
    from gallery import GalleryConfig

    pdf_path = tmp_path / "test.pdf"
    png_path = tmp_path / "test.png"

    png_path.write_text("fake png")
    import time

    time.sleep(0.1)
    pdf_path.write_text("fake pdf")

    convert_pdf_to_png(pdf_path, GalleryConfig(tmp_path))

    # Should convert since PDF is newer
    mock_convert.assert_called_once()


def test_calculate_directory_stats_empty(tmp_path):
    stats = calculate_directory_stats(tmp_path)

    assert stats["file_count"] == 0
    assert stats["folder_count"] == 0
    assert stats["total_size"] == 0
    assert stats["pdf_size"] == 0
    assert stats["png_size"] == 0


def test_calculate_directory_stats_with_files(tmp_path):
    # Create test files
    (tmp_path / "test.pdf").write_text("pdf content")
    (tmp_path / "test.png").write_text("png content")
    (tmp_path / "test.txt").write_text("txt content")

    # Create subdirectory
    subdir = tmp_path / "subdir"
    subdir.mkdir()
    (subdir / "nested.pdf").write_text("nested pdf")

    stats = calculate_directory_stats(tmp_path)

    assert stats["file_count"] == 4
    assert stats["folder_count"] == 1
    assert stats["total_size"] > 0
    assert stats["pdf_size"] > 0
    assert stats["png_size"] > 0


def test_calculate_directory_stats_nonexistent():
    nonexistent = Path("/nonexistent/path")
    stats = calculate_directory_stats(nonexistent)

    assert stats["file_count"] == 0
    assert stats["folder_count"] == 0
    assert stats["total_size"] == 0


def test_datetime_from_timestamp():
    timestamp = 1630000000  # Some Unix timestamp
    dt = datetime_from_timestamp(timestamp)

    assert isinstance(dt, datetime)
    assert dt.timestamp() == timestamp


def test_strftime_filter():
    dt = datetime(2023, 9, 8, 14, 30, 0)
    formatted = strftime_filter(dt, "%Y-%m-%d %H:%M")

    assert formatted == "2023-09-08 14:30"


@patch("gallery.builder.save_metadata_cache")
@patch("gallery.utils.processing.resolve_metadata_for_plot")
@patch("gallery.builder.merge_metadata")
@patch("gallery.builder.load_folder_metadata")
@patch("gallery.utils.processing.convert_pdf_to_png")
@patch("shutil.copy2")
def test_build_gallery_basic(
    mock_copy, mock_convert, mock_load_folder, mock_merge, mock_resolve, mock_save_cache, tmp_path
):
    from gallery import GalleryConfig, get_template

    source_dir = tmp_path / "source"
    web_dir = tmp_path / "web"
    source_dir.mkdir()
    web_dir.mkdir()

    # Create a test PDF
    pdf_file = source_dir / "test.pdf"
    pdf_file.write_text("fake pdf content")

    # Don't create PNG - this will trigger convert_pdf_to_png call

    # Mock returns
    mock_load_folder.return_value = {"folder": "metadata"}
    mock_merge.return_value = {"merged": "metadata"}
    mock_resolve.return_value = {"plot": "metadata"}

    config = GalleryConfig(tmp_path)
    build_gallery(config, source_dir, web_dir, template=get_template())

    # Verify mocks were called
    mock_convert.assert_called_once_with(pdf_file, config=config)
    mock_copy.assert_called()  # Should be called for PDF
    mock_save_cache.assert_called_once()

    # Check HTML file was created by the real render_gallery_page
    html_file = web_dir / "index.html"
    assert html_file.exists()


@patch("gallery.builder.render_gallery_page")
@patch("gallery.builder.save_metadata_cache")
@patch("gallery.builder.load_folder_metadata")
def test_build_gallery_with_subdirs(mock_load_folder, mock_save_cache, mock_template, tmp_path):
    source_dir = tmp_path / "source"
    web_dir = tmp_path / "web"
    source_dir.mkdir()
    web_dir.mkdir()

    # Create subdirectory
    subdir = source_dir / "subdir"
    subdir.mkdir()

    mock_load_folder.return_value = {}
    mock_template.render.return_value = "<html>test</html>"

    from gallery import GalleryConfig

    build_gallery(config=GalleryConfig(tmp_path), source_dir=source_dir, web_dir=web_dir)

    # Check subdirectory was created in web
    web_subdir = web_dir / "subdir"
    assert web_subdir.exists()
    assert web_subdir.is_dir()


@patch("gallery.utils.processing.needs_update")
@patch("shutil.copy2")
def test_build_gallery_skip_up_to_date(mock_copy, mock_needs_update, tmp_path):
    source_dir = tmp_path / "source"
    web_dir = tmp_path / "web"
    source_dir.mkdir()
    web_dir.mkdir()

    # Create test files
    pdf_file = source_dir / "test.pdf"
    png_file = source_dir / "test.png"
    pdf_file.write_text("pdf")
    png_file.write_text("png")

    # Create target files
    web_pdf = web_dir / "test.pdf"
    web_png = web_dir / "test.png"
    web_pdf.write_text("pdf")
    web_png.write_text("png")

    # Mock needs_update to return False (up to date)
    mock_needs_update.return_value = False

    from gallery import get_template

    get_template()
    from gallery import GalleryConfig

    build_gallery(GalleryConfig(tmp_path), source_dir, web_dir)

    # copy2 should not be called since files are up to date
    mock_copy.assert_not_called()
