# This is free software for the public good of a permacomputer hosted at
# permacomputer.com, an always-on computer by the people, for the people.
# One which is durable, easy to repair, & distributed like tap water
# for machine learning intelligence.
#
# The permacomputer is community-owned infrastructure optimized around
# four values:
#
#   TRUTH      First principles, math & science, open source code freely distributed
#   FREEDOM    Voluntary partnerships, freedom from tyranny & corporate control
#   HARMONY    Minimal waste, self-renewing systems with diverse thriving connections
#   LOVE       Be yourself without hurting others, cooperation through natural law
#
# This software contributes to that vision by archiving the web, preserving digital knowledge before it disappears.
# Code is seeds to sprout on any abandoned technology.

"""
Tests for archive module.

Tests site archiver functionality for creating distributable tar.gz packages.
"""

import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
import tempfile
import shutil
import os
import json
from pathlib import Path
from datetime import datetime

from archive import (
    SiteArchiver,
    sanitize_filename,
    url_to_path,
    html_to_markdown,
    HAS_HTML2TEXT,
)


class TestSanitizeFilename:
    """Test filename sanitization."""

    def test_simple_name(self):
        """Test simple name passes through."""
        result = sanitize_filename("example")
        assert result == "example"

    def test_removes_special_chars(self):
        """Test special characters are removed."""
        result = sanitize_filename("test<>:/\\|?*file")
        # Should not contain any of those characters
        for char in '<>:"/\\|?*':
            assert char not in result

    def test_domain_with_dots(self):
        """Test domain with dots is sanitized."""
        result = sanitize_filename("www.example.com")
        assert "." not in result

    def test_collapses_multiple_dashes(self):
        """Test multiple dashes are collapsed."""
        result = sanitize_filename("test---file")
        assert "---" not in result

    def test_strips_leading_trailing(self):
        """Test leading/trailing dashes are stripped."""
        result = sanitize_filename("--test--")
        assert not result.startswith("-")
        assert not result.endswith("-")

    def test_empty_string(self):
        """Test empty string returns 'unnamed'."""
        result = sanitize_filename("")
        assert result == "unnamed"

    def test_only_special_chars(self):
        """Test string of only special chars returns 'unnamed'."""
        result = sanitize_filename(":::///")
        assert result == "unnamed"

    def test_max_length(self):
        """Test max length truncation."""
        long_name = "a" * 300
        result = sanitize_filename(long_name)
        assert len(result) <= 200


class TestUrlToPath:
    """Test URL to filesystem path conversion."""

    def test_root_url(self):
        """Test root URL becomes index.html."""
        result = url_to_path("https://example.com/")
        assert result == "index.html"

    def test_root_url_with_path_slash(self):
        """Test root URL with explicit path slash."""
        # Note: url_to_path expects URLs to have at least a "/" path
        result = url_to_path("https://example.com/about/")
        assert "about" in result
        assert result.endswith("index.html")

    def test_html_file(self):
        """Test .html file preserves extension."""
        result = url_to_path("https://example.com/page.html")
        assert result == "page.html"

    def test_htm_file(self):
        """Test .htm file preserves extension."""
        result = url_to_path("https://example.com/page.htm")
        assert result == "page.htm"

    def test_directory_path(self):
        """Test directory path gets index.html."""
        result = url_to_path("https://example.com/about")
        assert result.endswith("index.html")
        assert "about" in result

    def test_nested_directory(self):
        """Test nested directory path."""
        result = url_to_path("https://example.com/blog/posts")
        assert "blog" in result
        assert "posts" in result
        assert result.endswith("index.html")

    def test_file_with_extension(self):
        """Test file with non-html extension."""
        result = url_to_path("https://example.com/document.pdf")
        assert result == "document.pdf"


class TestHtmlToMarkdown:
    """Test HTML to markdown conversion."""

    @pytest.mark.skipif(not HAS_HTML2TEXT, reason="html2text not installed")
    def test_simple_html(self):
        """Test converting simple HTML."""
        html = "<h1>Title</h1><p>Paragraph text.</p>"
        result = html_to_markdown(html)
        assert "Title" in result
        assert "Paragraph" in result

    @pytest.mark.skipif(not HAS_HTML2TEXT, reason="html2text not installed")
    def test_preserves_links(self):
        """Test links are preserved."""
        html = '<a href="https://example.com">Link</a>'
        result = html_to_markdown(html)
        assert "https://example.com" in result or "[Link]" in result

    @pytest.mark.skipif(not HAS_HTML2TEXT, reason="html2text not installed")
    def test_preserves_images(self):
        """Test images are preserved."""
        html = '<img src="image.jpg" alt="Test">'
        result = html_to_markdown(html)
        # Should have image reference
        assert "image.jpg" in result or "Test" in result

    def test_without_html2text(self):
        """Test fallback when html2text not available."""
        # When html2text is not installed, should return original HTML
        if not HAS_HTML2TEXT:
            html = "<h1>Test</h1>"
            result = html_to_markdown(html)
            assert result == html


class TestSiteArchiverInitialization:
    """Test SiteArchiver class initialization."""

    def test_default_initialization(self):
        """Test default initialization values."""
        archiver = SiteArchiver()

        assert archiver.output_dir == Path(".")
        assert archiver.include_screenshots is True
        assert archiver.include_markdown == HAS_HTML2TEXT
        assert archiver.fast_mode is False
        assert archiver.trim_wrapper is False
        assert archiver.show_progress is True
        assert archiver.fresh_start is False

    def test_custom_output_dir(self):
        """Test custom output directory."""
        archiver = SiteArchiver(output_dir="/tmp/archives")
        assert archiver.output_dir == Path("/tmp/archives")

    def test_disable_screenshots(self):
        """Test disabling screenshots."""
        archiver = SiteArchiver(include_screenshots=False)
        assert archiver.include_screenshots is False
        assert archiver.screenshot_config.enabled is False

    def test_disable_markdown(self):
        """Test disabling markdown."""
        archiver = SiteArchiver(include_markdown=False)
        assert archiver.include_markdown is False

    def test_fast_mode(self):
        """Test enabling fast mode."""
        archiver = SiteArchiver(fast_mode=True)
        assert archiver.fast_mode is True

    def test_trim_wrapper(self):
        """Test trim wrapper option."""
        archiver = SiteArchiver(trim_wrapper=True)
        assert archiver.trim_wrapper is True

    def test_fresh_start(self):
        """Test fresh start option."""
        archiver = SiteArchiver(fresh_start=True)
        assert archiver.fresh_start is True


class TestSiteArchiverExtractTitle:
    """Test SiteArchiver._extract_title method."""

    def setup_method(self):
        self.archiver = SiteArchiver()

    def test_extract_title_from_title_tag(self):
        """Test extracting title from <title> tag."""
        html = "<html><head><title>Page Title</title></head><body></body></html>"
        result = self.archiver._extract_title(html)
        assert result == "Page Title"

    def test_extract_title_from_h1(self):
        """Test extracting title from <h1> when no title tag."""
        html = "<html><body><h1>Heading Title</h1></body></html>"
        result = self.archiver._extract_title(html)
        assert result == "Heading Title"

    def test_extract_title_empty(self):
        """Test extracting title from empty HTML."""
        html = "<html><body></body></html>"
        result = self.archiver._extract_title(html)
        assert result is None

    def test_extract_title_invalid_html(self):
        """Test extracting title from invalid HTML."""
        html = "not html at all"
        result = self.archiver._extract_title(html)
        assert result is None


class TestSiteArchiverWriteIndex:
    """Test SiteArchiver._write_index_html method."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        self.archiver = SiteArchiver()

    def teardown_method(self):
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    def test_write_index_creates_file(self):
        """Test that index.html is created."""
        sitemap = [
            {"path": "html/page1.html", "title": "Page 1"},
            {"path": "html/page2.html", "title": "Page 2"},
        ]
        self.archiver._write_index_html(Path(self.temp_dir), sitemap, "example.com")

        index_path = Path(self.temp_dir) / "index.html"
        assert index_path.exists()

    def test_write_index_contains_domain(self):
        """Test that index contains domain name."""
        sitemap = []
        self.archiver._write_index_html(Path(self.temp_dir), sitemap, "example.com")

        index_path = Path(self.temp_dir) / "index.html"
        content = index_path.read_text()
        assert "example.com" in content

    def test_write_index_contains_links(self):
        """Test that index contains page links."""
        sitemap = [
            {"path": "html/page1.html", "title": "Page 1"},
        ]
        self.archiver._write_index_html(Path(self.temp_dir), sitemap, "example.com")

        index_path = Path(self.temp_dir) / "index.html"
        content = index_path.read_text()
        assert "Page 1" in content
        assert "html/page1.html" in content

    def test_write_index_limits_to_1000(self):
        """Test that index limits sitemap to 1000 entries."""
        sitemap = [{"path": f"html/page{i}.html", "title": f"Page {i}"} for i in range(1500)]
        self.archiver._write_index_html(Path(self.temp_dir), sitemap, "example.com")

        index_path = Path(self.temp_dir) / "index.html"
        content = index_path.read_text()
        # Should mention there are more pages
        assert "more pages" in content or "500 more" in content or "and" in content


class TestSiteArchiverArchive:
    """Test SiteArchiver.archive method."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()
        self.archiver = SiteArchiver(output_dir=self.temp_dir)

    def teardown_method(self):
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    @pytest.mark.asyncio
    async def test_archive_creates_tarball(self):
        """Test that archive creates a tar.gz file."""
        with patch('archive.NeoPig') as MockNeoPig:
            # Setup mocks
            mock_pig = AsyncMock()
            MockNeoPig.return_value = mock_pig
            mock_pig.init = AsyncMock()
            mock_pig.crawl = AsyncMock(return_value={})
            mock_pig.db = AsyncMock()
            mock_pig.db.get_all_media_uri_mappings = AsyncMock(return_value={})
            mock_pig.db.get_pages_by_domain = AsyncMock(return_value=[])
            mock_pig.db.close = AsyncMock()
            mock_pig.screenshot_config = MagicMock()
            mock_pig.screenshot_config.enabled = True
            mock_pig._clear_state = MagicMock()
            mock_pig.seen_pages = set()
            mock_pig.seen_media = {}
            mock_pig.seen_screenshots = set()

            # Run archive
            try:
                result = await self.archiver.archive(
                    "https://example.com",
                    depth=1,
                    max_pages=1,
                )
                # Check if tarball was created (might not be if mocking isn't complete)
                # assert result.suffix == ".gz" or str(result).endswith(".tar.gz")
            except Exception:
                pass  # Full archive requires extensive mocking


class TestHAS_HTML2TEXT:
    """Test html2text availability constant."""

    def test_has_html2text_is_bool(self):
        """Test HAS_HTML2TEXT is a boolean."""
        assert isinstance(HAS_HTML2TEXT, bool)


class TestArchiveIntegration:
    """Integration tests for archive functionality."""

    def setup_method(self):
        self.temp_dir = tempfile.mkdtemp()

    def teardown_method(self):
        if os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)

    def test_sanitize_domain_for_filename(self):
        """Test domain sanitization produces valid filename."""
        domains = [
            "example.com",
            "sub.example.com",
            "example.com:8080",
            "example.co.uk",
        ]
        for domain in domains:
            result = sanitize_filename(domain)
            # Should not contain problematic characters
            assert "/" not in result
            assert "\\" not in result
            assert ":" not in result
            # Should be valid filename
            assert len(result) > 0

    def test_url_to_path_for_common_patterns(self):
        """Test URL to path for common URL patterns."""
        test_cases = [
            ("https://example.com/", "index.html"),
            ("https://example.com/about", "about/index.html"),
            ("https://example.com/blog/", "blog/index.html"),
            ("https://example.com/doc.html", "doc.html"),
            ("https://example.com/file.pdf", "file.pdf"),
        ]
        for url, expected in test_cases:
            result = url_to_path(url)
            assert expected in result or result == expected


class TestUpgradeNeopigInArchive:
    """Test upgrade_neopig_in_archive function."""

    def test_function_exists(self):
        """Test function is importable."""
        from archive import upgrade_neopig_in_archive
        assert callable(upgrade_neopig_in_archive)


class TestArchiverOptions:
    """Test various archiver configuration options."""

    def test_screenshot_config_created(self):
        """Test screenshot config is created."""
        archiver = SiteArchiver(include_screenshots=True)
        assert archiver.screenshot_config is not None
        assert archiver.screenshot_config.enabled is True

    def test_screenshot_config_disabled(self):
        """Test screenshot config when disabled."""
        archiver = SiteArchiver(include_screenshots=False)
        assert archiver.screenshot_config.enabled is False

    def test_markdown_respects_html2text(self):
        """Test markdown option respects html2text availability."""
        archiver = SiteArchiver(include_markdown=True)
        # Should be True only if html2text is installed
        assert archiver.include_markdown == HAS_HTML2TEXT

    def test_output_dir_path_conversion(self):
        """Test output_dir is converted to Path."""
        archiver = SiteArchiver(output_dir="/custom/path")
        assert isinstance(archiver.output_dir, Path)
        assert str(archiver.output_dir) == "/custom/path"


if __name__ == '__main__':
    pytest.main([__file__, '-v'])
