import csv
import io
from abc import ABC, abstractmethod
from typing import List, Union

import openpyxl.cell.cell

from maps_adv.geosmb.harmonist.server.lib.exceptions import BadFileContent


class BaseParser(ABC):
    @abstractmethod
    def parse(self, content: Union[str, bytes]) -> List[List[str]]:
        raise NotImplementedError


class CsvParser(BaseParser):
    @classmethod
    def parse(cls, content: Union[str, bytes]) -> List[List[str]]:
        if isinstance(content, str):
            text = content
        elif isinstance(content, bytes):
            text = cls._parse_bytes(content)
        else:
            raise AssertionError(
                f"Unsupported content type: {type(content)}. str or bytes are expected."
            )

        return cls._parse_text(text)

    @staticmethod
    def _parse_bytes(content: bytes) -> str:
        try:
            return content.decode("utf-8")
        except UnicodeDecodeError:
            raise BadFileContent("Content of file can't be decoded as utf-8")

    @staticmethod
    def _parse_text(text: str) -> List[List[str]]:
        rows = csv.reader(text.splitlines(), delimiter=";")

        return [
            [cell.strip() for cell in row]
            for row in rows
            if any(cell.strip() for cell in row)
        ]


class XLSXParser(BaseParser):
    @classmethod
    def parse(cls, content: bytes) -> List[List[str]]:
        try:
            workbook = openpyxl.load_workbook(
                io.BytesIO(content), read_only=True, data_only=True
            )
        except Exception:
            raise BadFileContent("Content of file can't be parsed as XLSX file")

        try:
            return list(list(map(cls._get_cell_value, row)) for row in workbook.active)
        finally:
            workbook.close()

    @staticmethod
    def _get_cell_value(cell: openpyxl.cell.Cell) -> str:
        if cell.data_type == openpyxl.cell.cell.TYPE_ERROR or cell.value is None:
            return ""
        else:
            return str(cell.value)
