from __future__ import annotations

import logging
import typing
from csv import DictReader, DictWriter

import chardet

from ..base import BaseCodec, BaseFileIterable
from ..exceptions import FormatParseError
from ..helpers.utils import rowincount

DEFAULT_ENCODING = "utf8"
DEFAULT_DELIMITER = ","


def detect_encoding_raw(filename=None, stream=None, limit=1000000):
    if filename is not None:
        f = open(filename, "rb")
        chunk = f.read(limit)
        f.close()
    else:
        chunk = stream.read(limit)
        stream.reset()
    detected = chardet.detect(chunk)
    logging.debug("Detected encoding {}".format(detected["encoding"]))
    return detected


def detect_delimiter(filename=None, stream=None, encoding="utf8"):
    if filename is not None:
        f = open(filename, encoding=encoding)
        line = f.readline()
        f.close()
    else:
        line = stream.readline()
        #        stream.seek(0,0)
        stream.reset()
    dict1 = {",": line.count(","), ";": line.count(";"), "\t": line.count("\t"), "|": line.count("|")}
    delimiter = max(dict1, key=dict1.get)
    logging.debug(f"Detected delimiter {delimiter}")
    return delimiter


class CSVIterable(BaseFileIterable):
    def __init__(
        self,
        filename: str = None,
        stream: typing.IO = None,
        codec: BaseCodec = None,
        keys: list[str] = None,
        delimiter: str = None,
        quotechar: str = '"',
        mode: str = "r",
        encoding: str = None,
        autodetect: bool = False,
        options: dict = None,
    ):
        if options is None:
            options = {}
        logging.debug(f"Params: encoding: {encoding}, options {options}")
        self.encoding = None
        self.fileobj = stream
        if encoding is not None:
            self.encoding = encoding
        elif "encoding" in options.keys() and options["encoding"] is not None:
            self.encoding = options["encoding"]
        if mode == "r":
            if filename is not None and self.encoding is None:
                self.encoding = detect_encoding_raw(filename=filename)["encoding"]
            elif stream is not None and self.encoding is None:
                self.encoding = detect_encoding_raw(stream=stream)["encoding"]
            elif self.encoding is None:
                self.encoding = DEFAULT_ENCODING
        elif self.encoding is None:
            self.encoding = DEFAULT_ENCODING
        logging.debug(f"Final encoding {self.encoding}")
        self.keys = keys

        super().__init__(
            filename, stream, codec=codec, binary=False, encoding=self.encoding, mode=mode, options=options
        )
        if not delimiter:
            if autodetect and mode == "r":
                #                print(filename, stream)
                self.delimiter = detect_delimiter(filename, self.fobj, encoding=self.encoding)
            else:
                self.delimiter = DEFAULT_DELIMITER
        else:
            self.delimiter = delimiter
        self.quotechar = quotechar
        logging.debug(f"Detected delimiter {self.delimiter}")
        self.reset()
        pass

    @staticmethod
    def has_totals():
        """Has totals indicator"""
        return True

    def totals(self):
        """Returns file totals"""
        return rowincount(self.filename, self.fobj)

    def reset(self):
        super().reset()
        if self.fobj is None and self.codec is not None:
            fobj = self.codec.textIO(self.encoding)
        else:
            fobj = self.fobj
        logging.debug(f"Detected delimiter {self.delimiter}")
        self.reader = None
        if self.mode == "r":
            if self.keys is not None:
                self.reader = DictReader(fobj, fieldnames=self.keys, delimiter=self.delimiter, quotechar=self.quotechar)
            else:
                self.reader = DictReader(fobj, delimiter=self.delimiter, quotechar=self.quotechar)
        if self.mode in ["w", "wr"] and self.keys is not None:
            self.writer = DictWriter(fobj, fieldnames=self.keys, delimiter=self.delimiter, quotechar=self.quotechar)
            self.writer.writeheader()
        else:
            self.writer = None

        #            self.reader = reader(self.fobj, delimiter=self.delimiter, quotechar=self.quotechar)
        self.pos = 0
        # Reset line tracking for error context
        self._current_line = None
        self._current_line_number = 0
        self._current_byte_offset = 0

    @staticmethod
    def id() -> str:
        return "csv"

    @staticmethod
    def is_flatonly() -> bool:
        return True

    def read(self, skip_empty: bool = True):
        """Read single CSV record"""
        while True:
            try:
                # Try to get the raw line before parsing
                # DictReader doesn't expose the raw line, so we need to track it
                # For now, we'll track line number and try to get byte offset
                if hasattr(self.fobj, "tell"):
                    try:
                        self._current_byte_offset = self.fobj.tell()
                    except (OSError, AttributeError):
                        pass
                
                row = next(self.reader)
                self._current_line_number = self.reader.line_num
                
                # Try to get the original line if possible
                # Note: DictReader doesn't expose raw lines, so we'll set it to None
                # Format-specific implementations can override this
                self._current_line = None
                
                if skip_empty and len(row) == 0:
                    continue
                
                self.pos += 1
                return row
            except StopIteration:
                raise
            except Exception as e:
                # Handle parse errors according to error policy
                error = FormatParseError(
                    format_id="csv",
                    message=str(e),
                    filename=self.filename,
                    row_number=self._current_line_number if self._current_line_number > 0 else self.pos + 1,
                    byte_offset=self._current_byte_offset if self._current_byte_offset > 0 else None,
                    original_line=self._current_line,
                )
                self._handle_error(
                    error,
                    row_number=self._current_line_number if self._current_line_number > 0 else self.pos + 1,
                    byte_offset=self._current_byte_offset if self._current_byte_offset > 0 else None,
                    original_line=self._current_line,
                )
                # If we get here, error was handled (skip/warn), continue to next record
                continue

    def read_bulk(self, num: int = 10) -> list[dict]:
        """Read bulk CSV records efficiently"""
        chunk = []
        for _n in range(0, num):
            try:
                if hasattr(self.fobj, "tell"):
                    try:
                        self._current_byte_offset = self.fobj.tell()
                    except (OSError, AttributeError):
                        pass
                
                row = next(self.reader)
                self._current_line_number = self.reader.line_num
                self._current_line = None
                
                chunk.append(row)
                self.pos += 1
            except StopIteration:
                break
            except Exception as e:
                # Handle parse errors according to error policy
                error = FormatParseError(
                    format_id="csv",
                    message=str(e),
                    filename=self.filename,
                    row_number=self._current_line_number if self._current_line_number > 0 else self.pos + 1,
                    byte_offset=self._current_byte_offset if self._current_byte_offset > 0 else None,
                    original_line=self._current_line,
                )
                self._handle_error(
                    error,
                    row_number=self._current_line_number if self._current_line_number > 0 else self.pos + 1,
                    byte_offset=self._current_byte_offset if self._current_byte_offset > 0 else None,
                    original_line=self._current_line,
                )
                # If we get here, error was handled (skip/warn), continue to next record
                continue
        return chunk

    def is_streaming(self) -> bool:
        """Returns True - CSV always streams row by row"""
        return True

    def write(self, record: dict):
        """Write single CSV record"""
        self.writer.writerow(record)

    def write_bulk(self, records: list[dict]):
        """Write bulk CSV records"""
        self.writer.writerows(records)
