from datetime import datetime

from vcm_file_uploader._types import (
    CompressionInfo,
    TransferConfig,
    UploadFileEntry,
    UploadResult,
    UploadSummary,
)


class TestTransferConfig:
    def test_defaults(self):
        cfg = TransferConfig()
        assert cfg.multipart_threshold == 128 * 1024 * 1024
        assert cfg.multipart_chunksize == 128 * 1024 * 1024
        assert cfg.max_concurrency == 8

    def test_custom_values(self):
        cfg = TransferConfig(multipart_threshold=64 * 1024 * 1024, max_concurrency=4)
        assert cfg.multipart_threshold == 64 * 1024 * 1024
        assert cfg.max_concurrency == 4

    def test_frozen(self):
        cfg = TransferConfig()
        try:
            cfg.max_concurrency = 16  # type: ignore[misc]
            assert False, "Should have raised"
        except AttributeError:
            pass


class TestUploadFileEntry:
    def test_required_fields(self):
        entry = UploadFileEntry(path="t01/amber.nc", full_path="/tmp/t01/amber.nc", size=1024)
        assert entry.path == "t01/amber.nc"
        assert entry.size == 1024
        assert entry.category is None
        assert entry.sha256 is None
        assert entry.reason == "unknown"
        assert entry.multipart is False

    def test_all_fields(self):
        entry = UploadFileEntry(
            path="t01/amber.nc",
            full_path="/tmp/t01/amber.nc",
            size=500_000_000,
            category="trajectory",
            sha256="abc123",
            reason="modified",
            multipart=True,
        )
        assert entry.category == "trajectory"
        assert entry.multipart is True


class TestCompressionInfo:
    def test_defaults(self):
        info = CompressionInfo()
        assert info.algorithm == "gzip"
        assert info.level == 6

    def test_custom(self):
        info = CompressionInfo(
            algorithm="gzip",
            level=6,
            original_bytes=200_000_000,
            compressed_bytes=40_000_000,
        )
        assert info.original_bytes == 200_000_000
        assert info.compressed_bytes == 40_000_000


class TestUploadResult:
    def test_success(self):
        result = UploadResult(
            local_path="/tmp/file.nc",
            s3_key="jobs/j1/a1/file.nc",
            size=1024,
            success=True,
        )
        assert result.success is True
        assert result.error is None
        assert isinstance(result.uploaded_at, datetime)

    def test_failure(self):
        result = UploadResult(
            local_path="/tmp/file.nc",
            s3_key="jobs/j1/a1/file.nc",
            size=0,
            success=False,
            error="Access denied",
        )
        assert result.success is False
        assert result.error == "Access denied"


class TestUploadSummary:
    def _make_result(self, path: str, size: int, success: bool) -> UploadResult:
        return UploadResult(
            local_path=f"/tmp/{path}",
            s3_key=f"jobs/j1/a1/{path}",
            size=size,
            success=success,
            error=None if success else "fail",
        )

    def test_all_succeeded(self):
        summary = UploadSummary(results=[
            self._make_result("a.nc", 100, True),
            self._make_result("b.nc", 200, True),
        ])
        assert summary.all_succeeded is True
        assert len(summary.succeeded) == 2
        assert len(summary.failed) == 0
        assert summary.total_bytes == 300

    def test_partial_failure(self):
        summary = UploadSummary(results=[
            self._make_result("a.nc", 100, True),
            self._make_result("b.nc", 200, False),
        ])
        assert summary.all_succeeded is False
        assert len(summary.succeeded) == 1
        assert len(summary.failed) == 1
        assert summary.total_bytes == 100

    def test_empty(self):
        summary = UploadSummary()
        assert summary.all_succeeded is False
        assert summary.total_bytes == 0

    def test_exit_code_all_succeeded(self):
        summary = UploadSummary(results=[
            self._make_result("a.nc", 100, True),
        ])
        assert summary.exit_code == 0

    def test_exit_code_partial_failure(self):
        summary = UploadSummary(results=[
            self._make_result("a.nc", 100, True),
            self._make_result("b.nc", 200, False),
        ])
        assert summary.exit_code == 1

    def test_exit_code_total_failure(self):
        summary = UploadSummary(results=[
            self._make_result("a.nc", 100, False),
        ])
        assert summary.exit_code == 2

    def test_exit_code_empty(self):
        summary = UploadSummary()
        assert summary.exit_code == 2
