from __future__ import annotations

import io
import os
from gzip import GzipFile
from typing import BinaryIO, List, Optional, Tuple, Union

from .. import files
from ..enums import FileType
from ..streams import EndianBinaryReader
from .CompressionHelper import BROTLI_MAGIC, GZIP_MAGIC

FileSourceType = Union[str, bytes, bytearray, io.IOBase, EndianBinaryReader, BinaryIO]


def file_name_without_extension(file_name: str) -> str:
    return os.path.join(os.path.dirname(file_name), os.path.splitext(os.path.basename(file_name))[0])


def list_all_files(directory: str) -> List[str]:
    return [
        val
        for sublist in [
            [os.path.join(dir_path, filename) for filename in filenames]
            for (dir_path, dirnames, filenames) in os.walk(directory)
            if ".git" not in dir_path
        ]
        for val in sublist
    ]


def find_all_files(directory: str, search_str: str) -> List[str]:
    return [
        val
        for sublist in [
            [os.path.join(dir_path, filename) for filename in filenames if search_str in filename]
            for (dir_path, dirnames, filenames) in os.walk(directory)
            if ".git" not in dir_path
        ]
        for val in sublist
    ]


def check_file_type(
    input_: FileSourceType,
) -> Tuple[FileType, EndianBinaryReader]:
    if isinstance(input_, str) and os.path.isfile(input_):
        reader = EndianBinaryReader(open(input_, "rb"))
    elif isinstance(input_, EndianBinaryReader):
        reader = input_
    else:
        reader = EndianBinaryReader(input_)

    if reader.Length < 20:
        return FileType.ResourceFile, reader

    signature = reader.read_string_to_null(20)

    reader.Position = 0
    if signature in [
        "UnityWeb",
        "UnityRaw",
        "\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa",
        "UnityFS",
    ]:
        return FileType.BundleFile, reader
    elif signature.startswith(("UnityWebData", "TuanjieWebData")):
        return FileType.WebFile, reader
    elif signature == "PK\x03\x04":
        return FileType.ZIP, reader
    else:
        if reader.Length < 128:
            return FileType.ResourceFile, reader

        magic = bytes(reader.read_bytes(2))
        reader.Position = 0
        if GZIP_MAGIC == magic:
            g_stream = GzipFile(fileobj=reader)
            g_reader = EndianBinaryReader(g_stream, endian="<")
            signature = g_reader.read_string_to_null(20)
            g_stream.close()
            if signature.startswith(("UnityWebData", "TuanjieWebData")):
                return FileType.WebFile, reader
        reader.Position = 0x20
        magic = bytes(reader.read_bytes(6))
        reader.Position = 0
        if BROTLI_MAGIC == magic:
            return FileType.WebFile, reader

        # check if AssetsFile
        old_endian = reader.endian
        # read as if assetsfile and check version
        # ReadHeader
        reader.Position = 0
        metadata_size = reader.read_u_int()
        file_size = reader.read_u_int()
        version = reader.read_u_int()
        data_offset = reader.read_u_int()

        if version >= 22:
            raw_endian = reader.read_u_byte()
            _endian = ">" if raw_endian else "<"
            _reserved = reader.read_bytes(3)
            metadata_size = reader.read_u_int()
            file_size = reader.read_long()
            data_offset = reader.read_long()
            _unknown = reader.read_long()  # unknown

        # reset
        reader.endian = old_endian
        reader.Position = 0
        # check info
        if any(
            (
                version < 0,
                version > 100,
                *[x < 0 or x > reader.Length for x in [file_size, metadata_size, version, data_offset]],
                file_size < metadata_size,
                file_size < data_offset,
            )
        ):
            return FileType.ResourceFile, reader
        else:
            return FileType.AssetsFile, reader


def parse_file(
    reader: EndianBinaryReader,
    parent: files.File,
    name: str,
    typ: Optional[FileType] = None,
    is_dependency: bool = False,
) -> Union[files.File, EndianBinaryReader]:
    if typ is None:
        typ, _ = check_file_type(reader)
    f = reader
    try:
        if typ == FileType.AssetsFile and not name.endswith(
            (
                ".resS",
                ".resource",
                ".config",
                ".xml",
                ".dat",
            )
        ):
            f = files.SerializedFile(reader, parent, name=name, is_dependency=is_dependency)
        elif typ == FileType.BundleFile:
            f = files.BundleFile(reader, parent, name=name, is_dependency=is_dependency)
        elif typ == FileType.WebFile:
            f = files.WebFile(reader, parent, name=name, is_dependency=is_dependency)
    except Exception as e:
        reader.seek(0)
        print(f"Error parsing file {name!r} as {typ}: {e}")
        raise e
    return f


def find_sensitive_path(dir: str, insensitive_path: str) -> Union[str, None]:
    parts = os.path.split(insensitive_path.strip(os.path.sep))

    sensitive_path = dir
    for part in parts:
        part_lower = part.lower()
        part = next(
            (name for name in os.listdir(sensitive_path) if name.lower() == part_lower),
            None,
        )
        if part is None:
            return None
        sensitive_path = os.path.join(sensitive_path, part)

    return sensitive_path
