"""tests for file_lib"""
import glob
import json
import os
import logging
import unittest

from pathlib import Path

from amilib.file_lib import FileLib
from amilib.util import Util

from test.resources import Resources
from test.test_all import AmiAnyTest

FILE_LIB = "file_lib"
PDF_LIB = "pdf_lib"
TEXT_LIB = "text_lib"
_SETUP = "_setup"
_TEARDOWN = "_teardown"
TEST = "test"

"""
a few tests on globbing, etc"""

logger = Util.get_logger(__name__)

class File0Test(AmiAnyTest):
    # nothing much here
    logger = logging.getLogger("test_file")
    TEST = "test"

    OPTIONS = [
        FILE_LIB,
        PDF_LIB,
        TEXT_LIB,
        _SETUP,
        _TEARDOWN,
    ]

    @classmethod
    def example_setup(cls, pyamix):
        """ setup test or examples
        these are general commands (i.e. not subcommands)
        :pyamix:
        """
        pyamix.run_commands([
            "--delete ${exam_temp}",
            "--copy ${examples_test.p} ${exam_temp} overwrite",
        ])

    @classmethod
    def example_teardown(cls, pyamix):
        """ clean example files

        """
        pyamix.run_command([
            "--delete", "${exam_temp}",
        ])

    @classmethod
    def run_arg_tests(cls, args):
        """This needs revision , maybe using Examples()"""
        cls.logger.warning(f"*****running tests : {args[TEST]}")
        if not args[TEST]:
            cls.logger.warning(f"No tests given: choose some/all of {TEST}")
            return
        if FILE_LIB in args[TEST]:
            cls.logger.warning("run test_file")
            cls.test_file.main()
        if PDF_LIB in args[TEST]:
            cls.logger.warning("run test_pdf")
            cls.test_pdf.test_read_pdf()
        if TEXT_LIB in args[TEST]:
            cls.logger.warning("run test_text NYI")


    def test_convert_windows_file_names_to_posix(self):
        """ChatGPT"""

        # Example mixed paths
        mixed_paths = [
            "C:\\Users\\Alice\\Documents\\report.docx",
            "/home/bob/projects/script.sh",
            "D:\\Music\\Jazz\\track.mp3",
            "relative/path/to/file.txt",
            "E:/Videos/Clips/movie.mp4",  # Note: sometimes Windows uses forward slashes too
            "..\\Downloads\\file.zip"
        ]

        # Normalize all to POSIX
        posix_paths = [FileLib.normalize_to_posix(p) for p in mixed_paths]

        assert posix_paths == [
            "C:/Users/Alice/Documents/report.docx",
            "/home/bob/projects/script.sh",
            "D:/Music/Jazz/track.mp3",
            "relative/path/to/file.txt",
            "E:/Videos/Clips/movie.mp4",
            "../Downloads/file.zip"
            ]

    def test_list_children(self):
        """
        test that children can be listed as dir/not_dir. Ignore hidden files as these
        are not very standard
        """
        top_dir = self.make_self_test_dir()
        if Path(top_dir).exists():
            FileLib.delete_directory_contents(top_dir, delete_directory=True)
        assert not top_dir.exists()
        top_dir.mkdir()
        assert top_dir.exists()
        dir1 = Path(top_dir, "dir1")
        dir1.mkdir()
        assert dir1.exists()
        file1 = Path(top_dir, "file1")
        file1.touch()
        assert file1.exists()
        files = [str(Path(f)) for f in FileLib.get_children(top_dir)]
        # Use dynamic paths instead of hardcoded ones for cross-platform compatibility
        expected_files = [str(Path(top_dir, "dir1")), str(Path(top_dir, "file1"))]
        assert sorted(files) == sorted(expected_files)
        
        files = [str(Path(f)) for f in FileLib.get_children(top_dir, dirx=True)]
        expected_dirs = [str(Path(top_dir, "dir1"))]
        assert sorted(files) == sorted(expected_dirs)
        
        files = [str(Path(f)) for f in FileLib.get_children(top_dir, dirx=False)]
        expected_files_only = [str(Path(top_dir, "file1"))]
        assert sorted(files) == sorted(expected_files_only)

    @classmethod

    def test_get_input_strings(self):
        """
        reads lists of words with optional split
        """
        words = FileLib.get_input_strings("foo")
        assert words == ["foo"]
        words = FileLib.get_input_strings(["foo"])
        assert words == ["foo"]
        words = FileLib.get_input_strings(["foo", "bar"])
        assert words == ["foo", "bar"]
        words = FileLib.get_input_strings("foo bar")
        assert words == ["foo bar"]
        words = FileLib.get_input_strings("foo bar", split=True)
        assert words == ["foo", "bar"]

    def test_get_input_strings_from_file(self):
        """
        reads lists of words in file/s
        """
        words = FileLib.get_input_strings(Path(Resources.TEST_RESOURCES_DIR, "wordlists", "small_2.txt"))
        assert words == ["anthropogenic", "tropospheric"]
        words = FileLib.get_input_strings(Path(Resources.TEST_RESOURCES_DIR, "wordlists", "small_10.txt"))
        assert words == [
            'anthropogenic', 'physical-biogeochemical', 'peat drainage', 'tropospheric', 'permafrost',
            'centennial', 'aerosols', 'sequestration', 'albedo', 'exacerbating'
        ]
        words = FileLib.get_input_strings(Path(Resources.TEST_RESOURCES_DIR, "wordlists", "small_10.txt"), split=True)
        # assert words == [
        #     'anthropogenic', 'physical-biogeochemical', 'peat', 'drainage', 'tropospheric', 'permafrost',
        #     'centennial', 'aerosols', 'sequestration', 'albedo', 'exacerbating'
        # ]
        assert words ==['anthropogenic',
 'physical-biogeochemical',
 'peat',
 'drainage',
 'tropospheric',
 'permafrost',
 'centennial',
 'aerosols',
 'sequestration',
 'albedo',
 'exacerbating']
        # list of files
        words = FileLib.get_input_strings([
            Path(Resources.TEST_RESOURCES_DIR, "wordlists", "small_5.txt"),
            Path(Resources.TEST_RESOURCES_DIR, "wordlists", "small_10.txt")])

        # assert words == ['anthropogenic', 'physical-biogeochemical', 'peat drainage', 'tropospheric',
        #     'permafrost', 'centennial', 'aerosols', 'sequestration', 'albedo', 'exacerbating']
        assert words == ['anthropogenic',
 'tropospheric',
 'permafrost',
 'sequestration',
 'albedo',
 'anthropogenic',
 'physical-biogeochemical',
 'peat drainage',
 'tropospheric',
 'permafrost',
 'centennial',
 'aerosols',
 'sequestration',
 'albedo',
 'exacerbating']

        # list of files
        words = FileLib.get_input_strings([
            Path(Resources.TEST_RESOURCES_DIR, "wordlists", "small_5.txt"),
            Path(Resources.TEST_RESOURCES_DIR, "wordlists", "small_10.txt")], split=True)

        assert words == ['anthropogenic',
 'tropospheric',
 'permafrost',
 'sequestration',
 'albedo',
 'anthropogenic',
 'physical-biogeochemical',
 'peat',
 'drainage',
 'tropospheric',
 'permafrost',
 'centennial',
 'aerosols',
 'sequestration',
 'albedo',
 'exacerbating']


    def test_relative_pathname(self):
        """
        convert absolute pathname to relative to another path
        some of this is in Python 3.12 but we'll support earlier versions
        """
        abspath = Path("/a/b/c/d/e.txt")
        refpath = Path("/a/b/c/")
        diffpath = FileLib.get_relative_path(abspath, refpath)
        assert diffpath == Path("d/e.txt")

        diffpath = FileLib.get_relative_path(refpath, abspath)
        assert diffpath == Path("../..")

        # dont' use Python 3.12 walk_up
        refpath = Path("/a/b/c/x/y.txt")
        try:
            diffpath = FileLib.get_relative_path(abspath, refpath, walk_up=False)
        except ValueError as e:
            assert str(e) == "'/a/b/c/d/e.txt' is not in the subpath of '/a/b/c/x/y.txt'"

        # use Python 3.12 walk_up
        refpath = Path("/a/b/c/x/y/z.txt")

        diffpath = FileLib.get_relative_path(abspath, refpath, walk_up=True)
        assert diffpath == Path("../../../d/e.txt")
        diffpath = FileLib.get_relative_path(refpath, abspath, walk_up=True)
        assert diffpath == Path("../../x/y/z.txt")

    def test_force_write_operations(self):
        """Test basic file writing operations"""
        test_file = self.make_self_test_dir() / "test.txt"
        
        # Test basic write
        FileLib.force_write(test_file, "hello")
        self.assertTrue(test_file.exists())
        self.assertEqual(test_file.read_text(), "hello")
        
        # Test overwrite=False
        FileLib.force_write(test_file, "world", overwrite=False)
        self.assertEqual(test_file.read_text(), "hello")  # Should not change
        
        # Test nested path creation
        nested_file = self.test_dir / "a/b/c/test.txt"
        FileLib.force_write(nested_file, "nested")
        self.assertTrue(nested_file.exists())
        self.assertEqual(nested_file.read_text(), "nested")

    def test_copy_operations(self):
        """Test file and directory copying"""
        # Setup test files

        # source_dir = self.test_dir / "source" PMR - there is no self.test_dir
        source_dir = self.make_self_test_dir() / "source"
        source_dir.mkdir(exist_ok=True)
        assert source_dir.is_dir()
        test_txt = source_dir / "test.txt"
        test_txt = (test_txt)
        logger.debug(f"abs path {test_txt.absolute()}")
        test_txt.write_text("hello")
        assert test_txt.is_file() and not test_txt.is_dir()
        self.assertEqual(test_txt.read_text(), "hello")
        (source_dir / "subdir").mkdir(exist_ok=True)
        (source_dir / "subdir/test2.txt").write_text("world")
        
        # Test file copy
        dest_file = Path(self.test_dir, "dest.txt")
        FileLib.copy_file_or_directory(
            dest_file,
            test_txt,
            overwrite=True
        )
        self.assertTrue(dest_file.exists())
        self.assertEqual(dest_file.read_text(), "hello")
        
        # Test directory copy
        dest_dir = self.test_dir / "dest"
        FileLib.copy_file_or_directory(
            dest_dir,
            source_dir,
            overwrite=True
        )
        self.assertTrue((dest_dir / "test.txt").exists())
        self.assertTrue((dest_dir / "subdir/test2.txt").exists())

    def test_input_string_edge_cases(self):
        """Test edge cases for input string handling"""
        # Test empty inputs
        self.assertEqual(FileLib.get_input_strings(None), [])
        self.assertEqual(FileLib.get_input_strings([]), [])
        
        # Test mixed input types
        self.make_self_test_dir()
        test_file = self.test_dir / "words.txt"
        test_file.write_text("one\ntwo")
        
        result = FileLib.get_input_strings([
            "hello",
            test_file,
            "world"
        ])
        self.assertEqual(
            result,
            ["hello", "one", "two", "world"]
        )

    def make_self_test_dir(self):
        try:
            self.test_dir = self.__getattribute__("test_dir")
        except AttributeError as ae:
            self.test_dir = None
        if self.test_dir is None:
            self.test_dir = Path(Resources.TEMP_DIR, "test_files")
        self.test_dir.mkdir(exist_ok=True)
        return self.test_dir

    def test_read_python_dictionary(self):
        """
        writes a JSON string to temporary file and reads back into a python dictionary
        """
        pydict_str = '{"a" : "foo"}'
        file_lib_dir = Path(Resources.TEMP_DIR, "file_lib")
        FileLib.force_mkdir(file_lib_dir)
        tempdictfile = Path(file_lib_dir, "pydict.txt")

        logger.info(f"wrote json dict {tempdictfile}")
        with open (tempdictfile, "w", encoding="UTF-8") as f:
            f.write(pydict_str)
            pydict = json.loads(pydict_str)
        assert pydict is not None
        assert type(pydict) is dict
        assert pydict.get("a") == "foo"

class DownloadTest(AmiAnyTest):
    """    
    download tests
    """

    def test_service_connection_basic(self):
        """Test basic service connection functionality."""

        # Test with a reliable service
        result = FileLib.check_service_connection(
            service_url="https://httpbin.org/status/200",
            service_name="HTTPBin Test",
            timeout=10
        )
        
        self.assertIsInstance(result, dict)
        self.assertIn('connected', result)
        self.assertIn('status_code', result)
        self.assertIn('response_time', result)
        self.assertIn('error', result)
        self.assertIn('service', result)
        
        # If service is available, should be connected
        # If service is unavailable (503, timeout, etc.), we still verify the structure
        if result['connected']:
            self.assertEqual(result['status_code'], 200)
            self.assertIsInstance(result['response_time'], float)
            self.assertGreater(result['response_time'], 0)
        else:
            # Service unavailable - verify error handling
            # status_code may be None for timeouts, or have a value for HTTP errors
            self.assertIsNotNone(result['error'])
        
        self.assertEqual(result['service'], "HTTPBin Test")


    def test_service_connection_timeout(self):
        """Test service connection timeout handling."""

        # Test with a very short timeout to trigger timeout
        result = FileLib.check_service_connection(
            service_url="https://httpbin.org/delay/5",
            service_name="Timeout Test",
            timeout=1
        )
        
        self.assertFalse(result['connected'])
        self.assertIsNotNone(result['error'])
        # the error message is unpredictable so comment out
        # self.assertIn("timeout", result['error'].lower())
        self.assertEqual(result['service'], "Timeout Test")


    def test_service_connection_invalid_url(self):
        """Test service connection with invalid URL."""

        result = FileLib.check_service_connection(
            service_url="https://invalid-domain-that-does-not-exist-12345.com",
            service_name="Invalid URL Test",
            timeout=5
        )
        
        self.assertFalse(result['connected'])
        self.assertIsNotNone(result['error'])
        self.assertEqual(result['service'], "Invalid URL Test")


    def test_internet_connection(self):
        """Test basic internet connectivity."""

        result = FileLib.check_internet_connection(timeout=5)
        
        self.assertIsInstance(result, dict)
        self.assertEqual(result['service'], "Internet (Google DNS)")
        
        # Note: This test may fail if no internet connection
        # In that case, we just verify the structure is correct
        if result['connected']:
            self.assertEqual(result['status_code'], 200)
            self.assertIsInstance(result['response_time'], float)
        else:
            self.assertIsNotNone(result['error'])


def main():
    File0Test.test_file_simple()
    File0Test.test_templates()

if __name__ == "__main__":
    main()