import json
import os
import tempfile
import unittest.mock
from pathlib import Path
from orionis.test.cases.synchronous import SyncTestCase
from orionis.test.view.render import TestingResultRender

class TestTestingRender(SyncTestCase):

    def setUp(self):
        """
        Set up test fixtures before each test method.

        Creates a temporary directory for testing and prepares common test data
        that will be used across multiple test methods.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Create a temporary directory for test files
        self.temp_dir = tempfile.mkdtemp()
        self.storage_path = Path(self.temp_dir)

        # Sample test result data for testing
        self.sample_result = {
            "test_count": 5,
            "passed": 4,
            "failed": 1,
            "errors": 0,
            "duration": 2.5,
            "tests": [
                {"name": "test_one", "status": "passed"},
                {"name": "test_two", "status": "failed", "error": "AssertionError"},
            ],
        }

        # Sample list result for testing
        self.sample_list_result = [
            {"name": "test_a", "status": "passed"},
            {"name": "test_b", "status": "passed"},
        ]

    def tearDown(self):
        """
        Clean up test fixtures after each test method.

        Removes the temporary directory and all its contents to ensure
        a clean state for subsequent tests.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Clean up temporary directory
        import shutil
        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def testMethodsExist(self):
        """
        Test that required methods exist in the TestingResultRender class.

        This test verifies that all necessary methods are present in the
        TestingResultRender class by checking their existence using hasattr.

        Returns
        -------
        None
            This method does not return any value.
        """
        # List of method names that must exist in TestingResultRender
        required_methods = [
            "render",
        ]

        # Validate that each required method exists in the class
        for method_name in required_methods:

            # Assert that the method is present in TestingResultRender
            self.assertTrue(
                hasattr(TestingResultRender, method_name),
                f"{method_name} does not exist",
            )

    def testConstructorWithValidParameters(self):
        """
        Test TestingResultRender constructor with valid parameters.

        Verifies that the constructor properly initializes the instance
        when provided with valid input parameters including result data,
        storage path, filename, and persistence settings.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Create instance with valid parameters
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
            filename="test-report.html",
            persist=True,
        )

        # Verify instance was created successfully
        self.assertIsInstance(render, TestingResultRender)

        # Verify storage directory was created
        self.assertTrue(self.storage_path.exists())
        self.assertTrue(self.storage_path.is_dir())

    def testConstructorWithDictResult(self):
        """
        Test constructor accepts dictionary as result parameter.

        Verifies that the constructor properly handles dictionary input
        for the result parameter without raising validation errors.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Test with dictionary result
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
        )

        self.assertIsInstance(render, TestingResultRender)

    def testConstructorWithListResult(self):
        """
        Test constructor accepts list as result parameter.

        Verifies that the constructor properly handles list input
        for the result parameter without raising validation errors.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Test with list result
        render = TestingResultRender(
            result=self.sample_list_result,
            storage_path=self.storage_path,
        )

        self.assertIsInstance(render, TestingResultRender)

    def testConstructorWithStringStoragePath(self):
        """
        Test constructor accepts string as storage_path parameter.

        Verifies that the constructor properly handles string input
        for the storage_path parameter and converts it to a Path object.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Test with string storage path
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=str(self.storage_path),
        )

        self.assertIsInstance(render, TestingResultRender)

    def testConstructorWithPathStoragePath(self):
        """
        Test constructor accepts Path object as storage_path parameter.

        Verifies that the constructor properly handles Path object input
        for the storage_path parameter without requiring conversion.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Test with Path object storage path
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
        )

        self.assertIsInstance(render, TestingResultRender)

    def testConstructorCreatesStorageDirectory(self):
        """
        Test constructor creates storage directory if it doesn't exist.

        Verifies that the constructor automatically creates the storage
        directory structure when the specified path doesn't exist.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Use a non-existent subdirectory
        new_storage_path = self.storage_path / "new_directory" / "nested"

        # Verify directory doesn't exist initially
        self.assertFalse(new_storage_path.exists())

        # Create instance to trigger directory creation
        _ = TestingResultRender(
            result=self.sample_result,
            storage_path=new_storage_path,
        )

        # Verify directory was created
        self.assertTrue(new_storage_path.exists())
        self.assertTrue(new_storage_path.is_dir())

    def testConstructorWithInvalidResultType(self):
        """
        Test constructor raises ValueError for invalid result type.

        Verifies that the constructor properly validates the result parameter
        and raises a ValueError when provided with an invalid data type.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Test with invalid result types
        invalid_results = [
            "string_result",  # String
            123,              # Integer
            None,             # None
            {1, 2, 3},         # Set
        ]

        for invalid_result in invalid_results:
            with self.assertRaises(ValueError) as context:
                TestingResultRender(
                    result=invalid_result,
                    storage_path=self.storage_path,
                )

            self.assertIn("Result must be a dictionary or a list", str(context.exception))

    def testConstructorWithInvalidFilename(self):
        """
        Test constructor raises ValueError for invalid filename.

        Verifies that the constructor properly validates the filename parameter
        and raises a ValueError when provided with invalid filename values.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Test with invalid filename types
        invalid_filenames = [
            "",          # Empty string
            "   ",       # Whitespace only
            None,        # None
            123,          # Non-string
        ]

        for invalid_filename in invalid_filenames:
            with self.assertRaises(ValueError) as context:
                TestingResultRender(
                    result=self.sample_result,
                    storage_path=self.storage_path,
                    filename=invalid_filename,
                )

            self.assertIn("Filename must be a non-empty string", str(context.exception))

    def testConstructorWithInvalidStoragePath(self):
        """
        Test constructor raises ValueError for invalid storage_path type.

        Verifies that the constructor properly validates the storage_path parameter
        and raises a ValueError when provided with an invalid data type.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Test with invalid storage path types
        invalid_paths = [
            123,         # Integer
            None,        # None
            [],          # List
            {},           # Dictionary
        ]

        for invalid_path in invalid_paths:
            with self.assertRaises(ValueError) as context:
                TestingResultRender(
                    result=self.sample_result,
                    storage_path=invalid_path,
                )

            self.assertIn("Storage path must be a string or a Path object", str(context.exception))

    def testConstructorWithInvalidPersistType(self):
        """
        Test constructor raises ValueError for invalid persist type.

        Verifies that the constructor properly validates the persist parameter
        and raises a ValueError when provided with a non-boolean value.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Test with invalid persist types
        invalid_persist_values = [
            "true",      # String
            1,           # Integer
            None,        # None
            [],           # List
        ]

        for invalid_persist in invalid_persist_values:
            with self.assertRaises(ValueError) as context:
                TestingResultRender(
                    result=self.sample_result,
                    storage_path=self.storage_path,
                    persist=invalid_persist,
                )

            self.assertIn("Persist must be a boolean value", str(context.exception))

    def testRenderWithoutPersistence(self):
        """
        Test render method without persistence enabled.

        Verifies that the render method correctly generates an HTML report
        when persistence is disabled, using only the current result data.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Create render instance without persistence
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
            filename="test-memory-report.html",
            persist=False,
        )

        # Mock webbrowser.open to prevent actual browser opening
        with unittest.mock.patch("webbrowser.open"):
            # Generate the report
            report_path = render.render()

        # Verify report file was created
        self.assertTrue(Path(report_path).exists())
        self.assertTrue(report_path.endswith("test-memory-report.html"))

        # Read and verify report content
        with open(report_path, encoding="utf-8") as file:
            content = file.read()

        # Verify template placeholders were replaced
        self.assertIn("Memory", content)  # Persistence mode
        self.assertNotIn("{{orionis-testing-persistent}}", content)
        self.assertNotIn("{{orionis-testing-result}}", content)

        # Verify JSON data is present in content
        expected_json = json.dumps([self.sample_result], ensure_ascii=False, indent=None)
        self.assertIn(expected_json, content)

    @unittest.mock.patch("orionis.test.records.logs.TestLogs.get")
    def testRenderWithPersistence(self, mock_test_logs_get):
        """
        Test render method with persistence enabled.

        Verifies that the render method correctly generates an HTML report
        when persistence is enabled, retrieving data from the TestLogs database.

        Parameters
        ----------
        mock_test_logs_get : unittest.mock.Mock
            Mock object for the TestLogs.get method.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Mock the TestLogs.get method to return sample data
        mock_reports = [
            (1, json.dumps(self.sample_result)),
            (2, json.dumps({"test_count": 3, "passed": 2, "failed": 1})),
        ]
        mock_test_logs_get.return_value = mock_reports

        # Create render instance with persistence
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
            filename="test-db-report.html",
            persist=True,
        )

        # Mock webbrowser.open to prevent actual browser opening
        with unittest.mock.patch("webbrowser.open"):
            # Generate the report
            report_path = render.render()

        # Verify TestLogs.get was called with correct parameters
        mock_test_logs_get.assert_called_once_with(last=10)

        # Verify report file was created
        self.assertTrue(Path(report_path).exists())
        self.assertTrue(report_path.endswith("test-db-report.html"))

        # Read and verify report content
        with open(report_path, encoding="utf-8") as file:
            content = file.read()

        # Verify template placeholders were replaced
        self.assertIn("Database", content)  # Persistence mode
        self.assertNotIn("{{orionis-testing-persistent}}", content)
        self.assertNotIn("{{orionis-testing-result}}", content)

    @unittest.mock.patch("os.name", "nt")
    @unittest.mock.patch("webbrowser.open")
    def testRenderOpensWebBrowserOnWindows(self, mock_webbrowser_open):
        """
        Test render method opens web browser on Windows.

        Verifies that the render method attempts to open the generated report
        in the default web browser when running on Windows (os.name == 'nt').

        Parameters
        ----------
        mock_webbrowser_open : unittest.mock.Mock
            Mock object for the webbrowser.open function.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Skip test if not on Windows
        if os.name != "nt":
            self.skipTest("Web browser opening is only tested on Windows.")

        # Create render instance
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
            persist=False,
        )

        # Generate the report
        report_path = render.render()

        # Verify webbrowser.open was called with the report path URI
        expected_uri = Path(report_path).as_uri()
        mock_webbrowser_open.assert_called_once_with(expected_uri)

    def testRenderHandlesBrowserImportOnDifferentPlatforms(self):
        """
        Test render method handles browser import gracefully on different platforms.

        Verifies that the render method continues to work regardless of
        platform-specific browser handling, focusing on core functionality
        rather than platform-specific behavior.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Create render instance
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
            persist=False,
        )

        # Mock webbrowser.open to prevent actual browser opening
        with unittest.mock.patch("webbrowser.open"):
            # Generate the report - should work regardless of platform
            report_path = render.render()

        # Verify report was created successfully
        self.assertTrue(Path(report_path).exists())
        self.assertTrue(report_path.endswith("orionis-test-results.html"))

    def testRenderCreatesValidHtmlStructure(self):
        """
        Test render method creates valid HTML structure.

        Verifies that the generated HTML report contains the expected
        structural elements and basic HTML validity markers.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Create render instance
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
            filename="structure-test.html",
            persist=False,
        )

        # Mock webbrowser.open to prevent actual browser opening
        with unittest.mock.patch("webbrowser.open"):
            report_path = render.render()

        # Read and verify HTML structure
        with open(report_path, encoding="utf-8") as file:
            content = file.read()

        # Verify basic HTML structure
        self.assertIn("<!DOCTYPE html>", content)
        self.assertIn("<html", content)
        self.assertIn("<head>", content)
        self.assertIn("<body", content)  # Changed to allow attributes
        self.assertIn("</html>", content)

        # Verify title contains expected text
        self.assertIn("<title>", content)
        self.assertIn("Orionis", content)

    @unittest.mock.patch("webbrowser.open", side_effect=Exception("Browser error"))
    def testRenderHandlesWebBrowserException(self, mock_webbrowser_open):
        """
        Test render method handles web browser exceptions gracefully.

        Verifies that the render method continues to work normally even when
        the webbrowser.open function raises an exception, ensuring robustness.

        Parameters
        ----------
        mock_webbrowser_open : unittest.mock.Mock
            Mock object that raises an exception when called.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Skip test if not on Windows
        if os.name != "nt":
            self.skipTest("Web browser opening is only tested on Windows.")

        # Create render instance
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
            persist=False,
        )

        # Generate the report (should not raise exception despite webbrowser error)
        report_path = render.render()

        # Verify report was still created successfully
        self.assertTrue(Path(report_path).exists())

        # Verify webbrowser.open was attempted but failed
        mock_webbrowser_open.assert_called_once()

    @unittest.mock.patch("builtins.open", side_effect=FileNotFoundError("Template not found"))
    def testRenderHandlesTemplateFileNotFound(self, mock_builtin_open):
        """
        Test render method handles template file not found error.

        Verifies that the render method raises an appropriate exception
        when the report template file cannot be found or opened.

        Parameters
        ----------
        mock_builtin_open : unittest.mock.Mock
            Mock object that raises FileNotFoundError when called.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Create render instance
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
            persist=False,
        )

        # Verify that FileNotFoundError is raised when template is missing
        with self.assertRaises(FileNotFoundError):
            render.render()

    def testRenderReturnsAbsolutePath(self):
        """
        Test render method returns absolute path to generated report.

        Verifies that the render method returns the absolute file path
        to the generated HTML report file as a string.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Create render instance
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
            filename="absolute-path-test.html",
            persist=False,
        )

        # Mock webbrowser.open to prevent actual browser opening
        with unittest.mock.patch("webbrowser.open"):
            report_path = render.render()

        # Verify returned path is absolute and points to correct file
        self.assertTrue(os.path.isabs(report_path))
        self.assertTrue(report_path.endswith("absolute-path-test.html"))
        self.assertTrue(Path(report_path).exists())

    def testRenderWithCustomFilename(self):
        """
        Test render method with custom filename.

        Verifies that the render method correctly uses a custom filename
        when specified in the constructor, creating the report file with
        the expected name.

        Returns
        -------
        None
            This method does not return any value.
        """
        custom_filename = "my-custom-report.html"

        # Create render instance with custom filename
        render = TestingResultRender(
            result=self.sample_result,
            storage_path=self.storage_path,
            filename=custom_filename,
            persist=False,
        )

        # Mock webbrowser.open to prevent actual browser opening
        with unittest.mock.patch("webbrowser.open"):
            report_path = render.render()

        # Verify custom filename is used
        self.assertTrue(report_path.endswith(custom_filename))
        self.assertTrue(Path(report_path).exists())

    def testRenderWithComplexJsonData(self):
        """
        Test render method with complex JSON data containing special characters.

        Verifies that the render method correctly handles complex test result
        data including special characters, unicode strings, and nested structures.

        Returns
        -------
        None
            This method does not return any value.
        """
        # Complex result with special characters and unicode
        complex_result = {
            "test_name": "Tëst with spéçial chars & unicode: 你好",
            "description": 'Test containing "quotes" and \'apostrophes\'',
            "results": {
                "nested": ["item1", "item2", "itém3"],
                "symbols": '!@#$%^&*()_+-=[]{}|;:",.<>?',
            },
            "unicode_test": "测试数据 with émojis 🎉🚀",
        }

        # Create render instance
        render = TestingResultRender(
            result=complex_result,
            storage_path=self.storage_path,
            persist=False,
        )

        # Mock webbrowser.open to prevent actual browser opening
        with unittest.mock.patch("webbrowser.open"):
            report_path = render.render()

        # Verify report was created
        self.assertTrue(Path(report_path).exists())

        # Read and verify content contains complex data
        with open(report_path, encoding="utf-8") as file:
            content = file.read()

        # Verify special characters are properly encoded
        self.assertIn("Tëst with spéçial chars", content)
        self.assertIn("你好", content)
        self.assertIn("测试数据", content)
        self.assertIn("🎉🚀", content)
