from enum import Enum
from typing import Optional, List, Union

from datalogue.errors import DtlError, _enum_parse_error
from datalogue.dtl_utils import SerializableStringEnum, exists, _parse_datetime, _parse_uuid, _parse_list, map_option
from uuid import UUID
from datetime import datetime


class SearchOrder(Enum):
    created_at_asc = ("CreatedAt", "Asc")
    created_at_desc = ("CreatedAt", "Desc")
    updated_at_desc = ("UpdatedAt", "Desc")
    updated_at_asc = ("UpdatedAt", "Asc")


class RegexStatus(SerializableStringEnum):
    NoMatch = "NoMatch"
    PerfectMatch = "PerfectMatch"

    @staticmethod
    def parse_error(s: str) -> str:
        return DtlError(_enum_parse_error("regex status", s))

    @staticmethod
    def from_str(string: str) -> Union[DtlError, 'RegexStatus']:
        return SerializableStringEnum.from_str(RegexStatus)(string)


class RegexTestSample:
    """
    Representation for test samples for Regex. It contains a text value and 
    optionally a status to denote whether it's a Match or NoMatch
    """

    def __init__(self, text: str, status: Optional[RegexStatus] = None):
        self.text = text
        self.status = status

    def __repr__(self):
        return ('\n  RegexTestSample('
                f'text: {self.text!r}, '
                f'status: {self.status})')

    def __eq__(self, other):
        return (self.text == other.text) and \
               (self.status == other.status)

    @staticmethod
    def _as_payload(sample: 'RegexTestSample') -> Union[DtlError, dict]:
        payload = {"text": sample.text}
        if sample.status is not None:
            payload["status"] = sample.status.value
        return payload

    @staticmethod
    def _from_payload(payload: dict) -> Union[DtlError, 'Regex']:
        text = payload.get("text")
        if text is None:
            return DtlError("'text' is missing! Invalid json for RegexTestSample")
        status = map_option(payload.get("status"), RegexStatus.from_str)
        return RegexTestSample(text, status)


class Regex:
    """
    Representation for regexes.
    """

    def __init__(
            self,
            name: str,
            pattern: str,
            description: str = None,
            test_data: Optional[Union[List[str], List[RegexTestSample]]] = [],
            id: Optional[UUID] = None,
            owner: Optional[UUID] = None,
            editors: Optional[List[UUID]] = None,
            created_at: Optional[datetime] = None,
            updated_at: Optional[datetime] = None,
            updated_by: Optional[UUID] = None):
        self.id = id
        self.name = name
        self.pattern = pattern
        self.description = description
        if test_data is not None and all(isinstance(test_datum_text, str) for test_datum_text in test_data):
            self.test_data = list(map(lambda test_datum: RegexTestSample(test_datum), test_data))
        elif test_data is not None and all(
                isinstance(test_datum_text, RegexTestSample) for test_datum_text in test_data):
            self.test_data = test_data
        elif test_data is None:
            self.test_data = test_data
        else:
            raise DtlError("Please provide either list of strings or list of RegexTestSample for test_data parameters.")
        self.owner = owner
        self.editors = editors
        self.created_at = created_at
        self.updated_at = updated_at
        self.updated_by = updated_by

    def __eq__(self, other):
        return (self.name == other.name) and \
               (self.pattern == other.pattern) and \
               (self.description == other.description) and \
               (self.test_data == other.test_data) and \
               (self.owner == other.owner) and \
               (self.editors == other.editors) and \
               (self.created_at == other.created_at) and \
               (self.updated_at == other.updated_at) and \
               (self.updated_by == other.updated_by)

    def __repr__(self):
        return ('Regex('
                f'id: {self.id!r}, '
                f'name: {self.name!r}, '
                f'pattern: {self.pattern!r},  '
                f'description: {self.description!r}, '
                f'test_data: {self.test_data})')

    @staticmethod
    def _as_payload(regex: 'Regex') -> Union[DtlError, dict]:
        payload = {
            "name": regex.name,
            "pattern": regex.pattern,
            "description": regex.description,
            "testData": list(map(lambda t: RegexTestSample._as_payload(t), regex.test_data))
        }

        if regex.id is not None:
            payload["id"] = regex.id

        if regex.owner is not None:
            payload["owner"] = regex.owner

        if regex.editors is not None:
            payload["editors"] = regex.editors

        if regex.created_at is not None:
            payload["createdAt"] = regex.created_at

        if regex.updated_at is not None:
            payload["updatedAt"] = regex.updated_at

        if regex.updated_by is not None:
            payload["lastUpdateBy"] = regex.updated_by

        return payload

    @staticmethod
    def _from_payload(payload: dict) -> Union[DtlError, 'Regex']:
        name = payload.get("name")
        if name is None or not isinstance(name, str):
            return DtlError("'name' is missing or not a string")

        pattern = payload.get("pattern")
        if pattern is None or not isinstance(pattern, str):
            return DtlError("'pattern' is missing or not a string")

        description = payload.get("description")
        if description is None or not isinstance(description, str):
            return DtlError("'description' is missing or not a string")

        test_data_jsons = payload.get("testData")
        if test_data_jsons is None or not isinstance(test_data_jsons, List):
            return DtlError("'testData' is missing or not a List")

        test_data = _parse_list(RegexTestSample._from_payload)(test_data_jsons)
        if isinstance(test_data, DtlError):
            return test_data

        id = map_option(payload.get("id"), _parse_uuid)
        owner = map_option(payload.get("owner"), _parse_uuid)

        editors = payload.get("editors")
        if editors is not None:
            editors = _parse_list(_parse_uuid)(editors)
            if isinstance(editors, DtlError):
                return DtlError(f"'editors has at least one invalid value: {payload.get('editors')}", editors)

        created_at = map_option(payload.get("createdAt"), _parse_datetime)
        if isinstance(created_at, DtlError):
            return created_at

        updated_at = map_option(payload.get("updatedAt"), _parse_datetime)
        if isinstance(updated_at, DtlError):
            return updated_at

        updated_by = map_option(payload.get("lastUpdateBy"), _parse_uuid)
        if isinstance(updated_by, DtlError):
            return updated_by

        return Regex(name, pattern, description, test_data, id, owner, editors, created_at, updated_at, updated_by)



