#!/usr/bin/env python3
"""
Tests for the File Deduplicator - Python implementation.
Run with: python3 -m pytest tests/ -v
"""
import os
import sys
import tempfile
import shutil
import unittest
from pathlib import Path
from datetime import datetime

# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from core.scanner import FileScanner, FileInfo, DuplicateGroup
from actions.file_ops import FileManager, UndoManager, FileOperation


class TestFileScanner(unittest.TestCase):
    """Tests for FileScanner class."""
    
    def setUp(self):
        """Create temporary directory with test files."""
        self.test_dir = tempfile.mkdtemp()
        self.scanner = FileScanner()
        
        # Create test files
        self.file1 = Path(self.test_dir) / "file1.txt"
        self.file2 = Path(self.test_dir) / "file2.txt"
        self.file3 = Path(self.test_dir) / "file3.txt"
        self.unique_file = Path(self.test_dir) / "unique.txt"
        
        # Create duplicate files
        self.file1.write_text("duplicate content")
        self.file2.write_text("duplicate content")
        self.file3.write_text("duplicate content")
        
        # Create unique file
        self.unique_file.write_text("unique content")
    
    def tearDown(self):
        """Clean up temporary directory."""
        shutil.rmtree(self.test_dir)
    
    def test_scan_directory(self):
        """Test scanning a directory for duplicates."""
        duplicates = self.scanner.scan_directory(self.test_dir)
        
        self.assertEqual(len(duplicates), 1)
        self.assertEqual(self.scanner.stats["total_files"], 4)
        self.assertEqual(self.scanner.stats["duplicate_groups"], 1)
        self.assertEqual(self.scanner.stats["duplicate_files"], 3)
    
    def test_no_duplicates(self):
        """Test scanning directory with no duplicates."""
        # Remove duplicate files
        self.file2.unlink()
        self.file3.unlink()
        
        duplicates = self.scanner.scan_directory(self.test_dir)
        
        self.assertEqual(len(duplicates), 0)
        self.assertEqual(self.scanner.stats["duplicate_groups"], 0)
    
    def test_hash_calculation(self):
        """Test that duplicate files have same hash."""
        # Create files with identical content (same size forces hashing)
        content = "duplicate content here for testing purposes"
        self.file1.write_text(content)
        self.file2.write_text(content)
        self.file3.write_text(content)
        # Make unique file different size to avoid hash collision test
        self.unique_file.write_text("unique content different")
        
        self.scanner.scan_directory(self.test_dir)
        
        # Use resolved paths (macOS symlinks /var to /private/var)
        file1_path = str(self.file1.resolve())
        file2_path = str(self.file2.resolve())
        
        # All files should be in the files dict
        self.assertIn(file1_path, self.scanner.files)
        self.assertIn(file2_path, self.scanner.files)
        
        # Get hashes for duplicate files (they should be hashed since same size)
        hash1 = self.scanner.files[file1_path].hash
        hash2 = self.scanner.files[file2_path].hash
        
        self.assertEqual(hash1, hash2)
    
    def test_statistics(self):
        """Test statistics calculation."""
        self.scanner.scan_directory(self.test_dir)
        stats = self.scanner.get_statistics()
        
        self.assertEqual(stats["total_files"], 4)
        self.assertEqual(stats["duplicate_groups"], 1)
        self.assertEqual(stats["duplicate_files"], 3)
        # Wasted space = size of 2 duplicate files (keep 1)
        expected_waste = self.file1.stat().st_size * 2
        self.assertEqual(stats["wasted_space"], expected_waste)
    
    def test_invalid_directory(self):
        """Test scanning non-existent directory."""
        with self.assertRaises(ValueError):
            self.scanner.scan_directory("/nonexistent/path")


class TestDuplicateGroup(unittest.TestCase):
    """Tests for DuplicateGroup class."""
    
    def test_total_waste(self):
        """Test waste calculation."""
        files = [
            FileInfo(path="/file1.txt", size=100, hash="abc", 
                    modified=0, created=0, extension=".txt"),
            FileInfo(path="/file2.txt", size=100, hash="abc",
                    modified=0, created=0, extension=".txt"),
            FileInfo(path="/file3.txt", size=100, hash="abc",
                    modified=0, created=0, extension=".txt"),
        ]
        
        group = DuplicateGroup(hash="abc", size=100, files=files)
        
        # Keep 1, waste 2
        self.assertEqual(group.total_waste(), 200)
    
    def test_total_waste_single_file(self):
        """Test waste calculation with single file."""
        files = [
            FileInfo(path="/file1.txt", size=100, hash="abc",
                    modified=0, created=0, extension=".txt"),
        ]
        
        group = DuplicateGroup(hash="abc", size=100, files=files)
        
        self.assertEqual(group.total_waste(), 0)


class TestUndoManager(unittest.TestCase):
    """Tests for UndoManager class."""
    
    def setUp(self):
        """Create temporary directory for undo data."""
        self.test_dir = tempfile.mkdtemp()
        self.undo_manager = UndoManager(data_dir=self.test_dir)
    
    def tearDown(self):
        """Clean up temporary directory."""
        shutil.rmtree(self.test_dir)
    
    def test_create_batch(self):
        """Test creating a batch."""
        batch_id = self.undo_manager.create_batch("Test batch")
        
        self.assertIsNotNone(batch_id)
        self.assertIn(batch_id, self.undo_manager.batches)
    
    def test_add_operation(self):
        """Test adding operation to batch."""
        batch_id = self.undo_manager.create_batch("Test batch")
        
        op = FileOperation(
            operation_type="delete",
            source_path="/test/file.txt",
            dest_path="",
            timestamp=1234567890,
            size=100,
            hash=""
        )
        
        self.undo_manager.add_operation(batch_id, op)
        
        batch = self.undo_manager.batches[batch_id]
        self.assertEqual(len(batch.operations), 1)
    
    def test_get_recent_batches(self):
        """Test getting recent batches."""
        # Create multiple batches with different timestamps
        import time
        for i in range(5):
            self.undo_manager.create_batch(f"Batch {i}")
            time.sleep(0.01)  # Small delay to ensure different timestamps
        
        recent = self.undo_manager.get_recent_batches(3)
        
        self.assertEqual(len(recent), 3)
    
    def test_persistence(self):
        """Test that operations are persisted to disk."""
        import time
        batch_id = self.undo_manager.create_batch("Test batch")
        time.sleep(0.01)  # Ensure timestamp is saved
        
        # Force save
        self.undo_manager._save_operations()
        
        # Create new undo manager with same data dir
        new_manager = UndoManager(data_dir=self.test_dir)
        
        self.assertIn(batch_id, new_manager.batches)


class TestFileManager(unittest.TestCase):
    """Tests for FileManager class."""
    
    def setUp(self):
        """Create temporary directory for test files."""
        self.test_dir = tempfile.mkdtemp()
        self.data_dir = tempfile.mkdtemp()
        
        self.undo_manager = UndoManager(data_dir=self.data_dir)
        self.file_manager = FileManager(undo_manager=self.undo_manager, dry_run=False)
        
        # Create test file
        self.test_file = Path(self.test_dir) / "test.txt"
        self.test_file.write_text("test content")
    
    def tearDown(self):
        """Clean up temporary directories."""
        shutil.rmtree(self.test_dir)
        shutil.rmtree(self.data_dir)
    
    def test_delete_file(self):
        """Test deleting a file."""
        batch_id = self.file_manager.start_batch("Test delete")
        
        result = self.file_manager.delete_file(str(self.test_file))
        
        self.assertTrue(result)
        self.assertFalse(self.test_file.exists())
    
    def test_move_file(self):
        """Test moving a file."""
        batch_id = self.file_manager.start_batch("Test move")
        
        dest = Path(self.test_dir) / "moved.txt"
        result = self.file_manager.move_file(str(self.test_file), str(dest))
        
        self.assertTrue(result)
        self.assertFalse(self.test_file.exists())
        self.assertTrue(dest.exists())
    
    def test_dry_run(self):
        """Test dry run mode."""
        self.file_manager = FileManager(undo_manager=self.undo_manager, dry_run=True)
        batch_id = self.file_manager.start_batch("Test dry run")
        
        result = self.file_manager.delete_file(str(self.test_file))
        
        self.assertTrue(result)
        self.assertTrue(self.test_file.exists())  # File should still exist
    
    def test_no_active_batch(self):
        """Test operation without active batch."""
        with self.assertRaises(ValueError):
            self.file_manager.delete_file(str(self.test_file))


class TestFormatSize(unittest.TestCase):
    """Tests for size formatting."""
    
    def test_bytes(self):
        from actions.file_ops import FileManager
        fm = FileManager(UndoManager())
        
        self.assertEqual(fm.format_size(500), "500.00 B")
    
    def test_kilobytes(self):
        from actions.file_ops import FileManager
        fm = FileManager(UndoManager())
        
        self.assertEqual(fm.format_size(1024), "1.00 KB")
        self.assertEqual(fm.format_size(2048), "2.00 KB")
    
    def test_megabytes(self):
        from actions.file_ops import FileManager
        fm = FileManager(UndoManager())
        
        self.assertEqual(fm.format_size(1048576), "1.00 MB")


class TestTrashNaming(unittest.TestCase):
    """Tests for collision-resistant trash naming."""
    
    def setUp(self):
        self.test_dir = tempfile.mkdtemp()
        self.data_dir = tempfile.mkdtemp()
        self.undo_manager = UndoManager(data_dir=self.data_dir)
        self.file_manager = FileManager(undo_manager=self.undo_manager, dry_run=False)
    
    def tearDown(self):
        shutil.rmtree(self.test_dir)
        shutil.rmtree(self.data_dir)
    
    def test_unique_trash_names(self):
        """Test that multiple files with same name get unique trash paths."""
        from pathlib import Path
        
        # Create multiple files with same name in different locations
        test_files = []
        for i in range(5):
            subdir = Path(self.test_dir) / f"subdir_{i}"
            subdir.mkdir()
            test_file = subdir / "same_name.txt"
            test_file.write_text(f"content {i}")
            test_files.append(str(test_file))
        
        # Delete all files (they go to trash)
        batch_id = self.file_manager.start_batch("Test unique names")
        for test_file in test_files:
            self.file_manager.delete_file(test_file)
        
        # Check that all files were moved to trash with unique names
        trash_dir = Path(self.data_dir) / "trash" / datetime.now().strftime("%Y%m%d")
        trash_files = list(trash_dir.glob("same_name*.txt"))
        
        self.assertEqual(len(trash_files), 5, "Expected 5 unique trash files")
        
        # Verify all names are unique
        names = [f.name for f in trash_files]
        self.assertEqual(len(set(names)), 5, "Expected all trash file names to be unique")


class TestUndoVerification(unittest.TestCase):
    """Tests for undo verification feature."""
    
    def setUp(self):
        self.test_dir = tempfile.mkdtemp()
        self.data_dir = tempfile.mkdtemp()
        self.undo_manager = UndoManager(data_dir=self.data_dir)
        self.file_manager = FileManager(undo_manager=self.undo_manager, dry_run=False)
    
    def tearDown(self):
        shutil.rmtree(self.test_dir)
        shutil.rmtree(self.data_dir)
    
    def test_undo_verifies_file_restored(self):
        """Test that undo verifies file actually appears at target location."""
        from pathlib import Path
        
        # Create test file
        test_file = Path(self.test_dir) / "test.txt"
        test_file.write_text("test content")
        
        # Delete file
        batch_id = self.file_manager.start_batch("Test undo")
        self.file_manager.delete_file(str(test_file))
        
        # Verify file is in trash
        trash_dir = Path(self.data_dir) / "trash" / datetime.now().strftime("%Y%m%d")
        trash_files = list(trash_dir.glob("test*.txt"))
        self.assertEqual(len(trash_files), 1)
        
        # Undo
        success, messages = self.undo_manager.undo_batch(batch_id)
        
        # Verify undo succeeded and file was restored
        self.assertTrue(success, f"Undo failed: {messages}")
        self.assertTrue(test_file.exists(), "File should be restored after undo")
        
        # Verify messages contain summary
        summary_found = any("restored" in msg.lower() for msg in messages)
        self.assertTrue(summary_found, "Expected undo summary in messages")
    
    def test_undo_reports_missing_file(self):
        """Test that undo reports when file doesn't appear at target."""
        from pathlib import Path
        
        # Create test file
        test_file = Path(self.test_dir) / "test.txt"
        test_file.write_text("test content")
        
        # Delete file
        batch_id = self.file_manager.start_batch("Test undo")
        self.file_manager.delete_file(str(test_file))
        
        # Manually remove from trash (simulate corruption/failure)
        trash_dir = Path(self.data_dir) / "trash" / datetime.now().strftime("%Y%m%d")
        trash_files = list(trash_dir.glob("test*.txt"))
        if trash_files:
            trash_files[0].unlink()
        
        # Undo should fail gracefully
        success, messages = self.undo_manager.undo_batch(batch_id)
        
        # Verify undo reported failure
        self.assertFalse(success, "Undo should fail when backup missing")
        error_found = any("no backup found" in msg.lower() for msg in messages)
        self.assertTrue(error_found, "Expected error message about missing backup")


if __name__ == "__main__":
    unittest.main()
