import glob
import shutil
from dataclasses import dataclass, field
from pathlib import Path
from time import time
from typing import Any, Generator

from pydantic import Field, Secret

from unstructured_ingest.v2.interfaces import (
    AccessConfig,
    ConnectionConfig,
    Downloader,
    DownloaderConfig,
    DownloadResponse,
    FileData,
    FileDataSourceMetadata,
    Indexer,
    IndexerConfig,
    SourceIdentifiers,
    Uploader,
    UploaderConfig,
)
from unstructured_ingest.v2.logger import logger
from unstructured_ingest.v2.processes.connector_registry import (
    DestinationRegistryEntry,
    SourceRegistryEntry,
)

CONNECTOR_TYPE = "local"


class LocalAccessConfig(AccessConfig):
    pass


class LocalConnectionConfig(ConnectionConfig):
    access_config: Secret[LocalAccessConfig] = Field(
        default=LocalAccessConfig(), validate_default=True
    )


class LocalIndexerConfig(IndexerConfig):
    input_path: Path = Field(
        description="Path to the location in the local file system that will be processed."
    )
    recursive: bool = Field(
        default=False,
        description="Recursively download files in their respective folders "
        "otherwise stop at the files in provided folder level.",
    )

    @property
    def path(self) -> Path:
        return Path(self.input_path).resolve()


@dataclass
class LocalIndexer(Indexer):
    index_config: LocalIndexerConfig
    connection_config: LocalConnectionConfig = field(
        default_factory=lambda: LocalConnectionConfig()
    )
    connector_type: str = CONNECTOR_TYPE

    def list_files(self) -> list[Path]:
        input_path = self.index_config.path
        if input_path.is_file():
            return [Path(s) for s in glob.glob(f"{self.index_config.path}")]
        files = []
        if self.index_config.recursive:
            files.extend(list(input_path.rglob("*")))
        else:
            files.extend(list(input_path.glob("*")))
        return [f for f in files if f.is_file()]

    def get_file_metadata(self, path: Path) -> FileDataSourceMetadata:
        stats = path.stat()
        try:
            date_modified = str(stats.st_mtime)
        except Exception as e:
            logger.warning(f"Couldn't detect date modified: {e}")
            date_modified = None

        try:
            date_created = str(stats.st_birthtime)
        except Exception as e:
            logger.warning(f"Couldn't detect date created: {e}")
            date_created = None

        try:
            mode = stats.st_mode
            permissions_data = [{"mode": mode}]
        except Exception as e:
            logger.warning(f"Couldn't detect file mode: {e}")
            permissions_data = None

        try:
            filesize_bytes = stats.st_size
        except Exception as e:
            logger.warning(f"Couldn't detect file size: {e}")
            filesize_bytes = None

        return FileDataSourceMetadata(
            date_modified=date_modified,
            date_created=date_created,
            date_processed=str(time()),
            permissions_data=permissions_data,
            record_locator={"path": str(path.resolve())},
            filesize_bytes=filesize_bytes,
        )

    def run(self, **kwargs: Any) -> Generator[FileData, None, None]:
        for file_path in self.list_files():
            file_data = FileData(
                identifier=str(file_path.resolve()),
                connector_type=CONNECTOR_TYPE,
                source_identifiers=SourceIdentifiers(
                    fullpath=str(file_path.resolve()),
                    filename=file_path.name,
                    rel_path=(
                        str(file_path.resolve()).replace(str(self.index_config.path.resolve()), "")[
                            1:
                        ]
                        if not self.index_config.path.is_file()
                        else self.index_config.path.name
                    ),
                ),
                metadata=self.get_file_metadata(path=file_path),
            )
            yield file_data


class LocalDownloaderConfig(DownloaderConfig):
    pass


@dataclass
class LocalDownloader(Downloader):
    connector_type: str = CONNECTOR_TYPE
    connection_config: LocalConnectionConfig = field(default_factory=LocalConnectionConfig)
    download_config: LocalDownloaderConfig = field(default_factory=LocalDownloaderConfig)

    def get_download_path(self, file_data: FileData) -> Path:
        return Path(file_data.source_identifiers.fullpath)

    def run(self, file_data: FileData, **kwargs: Any) -> DownloadResponse:
        return DownloadResponse(
            file_data=file_data, path=Path(file_data.source_identifiers.fullpath)
        )


class LocalUploaderConfig(UploaderConfig):
    output_dir: str = Field(
        default="structured-output", description="Local path to write partitioned output to"
    )

    @property
    def output_path(self) -> Path:
        return Path(self.output_dir).resolve()

    def __post_init__(self):
        if self.output_path.exists() and self.output_path.is_file():
            raise ValueError("output path already exists as a file")


@dataclass
class LocalUploader(Uploader):
    connector_type: str = CONNECTOR_TYPE
    upload_config: LocalUploaderConfig = field(default_factory=LocalUploaderConfig)
    connection_config: LocalConnectionConfig = field(
        default_factory=lambda: LocalConnectionConfig()
    )

    def is_async(self) -> bool:
        return False

    def run(self, path: Path, file_data: FileData, **kwargs: Any) -> None:
        if source_identifiers := file_data.source_identifiers:
            rel_path = (
                source_identifiers.relative_path[1:]
                if source_identifiers.relative_path.startswith("/")
                else source_identifiers.relative_path
            )
            new_path = self.upload_config.output_path / Path(rel_path)
            final_path = str(new_path).replace(
                source_identifiers.filename, f"{source_identifiers.filename}.json"
            )
        else:
            final_path = self.upload_config.output_path / Path(f"{file_data.identifier}.json")
        Path(final_path).parent.mkdir(parents=True, exist_ok=True)
        logger.debug(f"copying file from {path} to {final_path}")
        shutil.copy(src=str(path), dst=str(final_path))


local_source_entry = SourceRegistryEntry(
    indexer=LocalIndexer,
    indexer_config=LocalIndexerConfig,
    downloader=LocalDownloader,
    downloader_config=LocalDownloaderConfig,
    connection_config=LocalConnectionConfig,
)

local_destination_entry = DestinationRegistryEntry(
    uploader=LocalUploader, uploader_config=LocalUploaderConfig
)
