"""The module contains a fingerprinter for CSV files supporting Gzipped and raw CSV files."""

from __future__ import annotations

import gzip
import hashlib
from concurrent import futures
from itertools import islice
from pathlib import Path

from mleko.utils.custom_logger import CustomLogger

from .base_fingerprinter import BaseFingerprinter


logger = CustomLogger()
"""The logger for the module."""


class CSVFingerprinter(BaseFingerprinter):
    """A fingerprinter for CSV files supporting Gzipped and raw CSV files."""

    def __init__(self, n_rows: int = 1000):
        """Initialize the CSVFingerprinter.

        Warning:
            The fingerprint is generated by reading the first `n_rows` of each CSV file. If the
            CSV file is larger than `n_rows`, only the first `n_rows` are read. This means that
            the fingerprint is not unique for the entire CSV file, but only for the first `n_rows`.

        Args:
            n_rows: The number of rows to sample from each CSV file for fingerprinting.

        Examples:
            >>> fingerprinter = CSVFingerprinter(n_rows=1000)
            >>> fingerprinter.fingerprint(["data.csv", "data2.csv"])
            "fingerprint"
        """
        self._n_rows = n_rows

    def fingerprint(self, data: list[str] | list[Path]) -> str:
        """Generate a fingerprint for the given list of CSV files.

        The currently supported file types are `.csv`, `.gz`, and `.csv.gz`.

        Args:
            data: A list of file paths to CSV files.

        Returns:
            The fingerprint as a hexadecimal string.
        """
        file_posix_paths: list[Path] = [Path(file_path) for file_path in data]
        with futures.ThreadPoolExecutor(max_workers=None) as executor:
            file_fingerprints = list(executor.map(self._fingerprint_csv_file, file_posix_paths))

        file_fingerprints.sort()
        fingerprint = hashlib.md5("".join(file_fingerprints).encode()).hexdigest()
        return fingerprint

    def _fingerprint_csv_file(self, file_path: Path) -> str:
        """Generate a fingerprint for a single CSV file.

        Args:
            file_path: The file path to a CSV file.

        Raises:
            ValueError: File is unsupported file type.

        Returns:
            The fingerprint as a hexadecimal string.
        """
        if file_path.suffix not in {".csv", ".gz", ".csv.gz"}:
            msg = f"Unsupported file type: {file_path.suffix}"
            logger.error(msg)
            raise ValueError(msg)

        if file_path.suffix in {".gz", ".csv.gz"}:
            with gzip.open(file_path, "rb") as f:
                sample = b"".join(islice((f.readline() for _ in range(self._n_rows)), self._n_rows))
        else:
            with open(file_path, "rb") as f:
                sample = b"".join(islice((f.readline() for _ in range(self._n_rows)), self._n_rows))
        fingerprint = hashlib.md5(str(sample).encode()).hexdigest()
        return fingerprint
