"""This module declareds the pydantic ORM representation for users."""

from datetime import datetime
from abc import ABC
from typing import Optional
from re import compile

from pydantic import BaseModel, validator

from todo.schemas.common import is_valid_id


def is_valid_email_format(input_str: str) -> bool:
    """Checks whether the input string is a valid email address format.

    Uses a regular expression to perform the check.
    """

    if not isinstance(input_str, str):
        raise TypeError(f"Expected a string but got {type(input_str)}.")

    regex = compile(r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$')
    if regex.fullmatch(input_str):
        return True
    else:
        return False


class AbstractUserValidator(BaseModel, ABC):
    """Base class for user validators shared with child classes."""

    @validator('email', check_fields=False)
    def assert_email_is_valid(cls, email):
        if email is not None:
            if not is_valid_email_format(email):
                raise ValueError("Invalid email address format.")
        return email

    @validator('first_name', check_fields=False)
    def assert_first_name_is_valid(cls, first_name):
        if first_name is not None:
            if not len(first_name):
                raise ValueError("First Name must not be empty.")
        return first_name

    @validator('last_name', check_fields=False)
    def assert_last_name_is_valid(cls, last_name):
        if last_name is not None:
            if not len(last_name):
                raise ValueError("Last Name must not be empty.")
        return last_name


class AbstractUser(AbstractUserValidator, ABC):
    """Base class for user attributes shared with child classes."""

    email: str
    first_name: str
    last_name: str
    is_admin: bool


class UserLogin(AbstractUserValidator, ABC):
    """Schema used during user login"""

    email: str
    password: str


class UserCreate(AbstractUser):
    """Schema for user creation."""

    password: str
    password_confirmation: str

    @validator('password_confirmation')
    def assert_passwords_match(cls, password_confirmation, values):
        if not password_confirmation == values['password']:
            raise ValueError("Passwords do not match.")
        if len(password_confirmation) < 1:
            # TODO use more robust password rules
            raise ValueError("Password must not be empty.")
        return password_confirmation


class UserUpdate(AbstractUserValidator):
    """Schema for user info updates.

    All fields here are optional, but passwords must match if at least one was
    provided.
    """

    email: Optional[str]
    first_name: Optional[str]
    last_name: Optional[str]
    is_admin: Optional[bool]

    password: Optional[str] = None
    password_confirmation: Optional[str] = None

    @validator('password_confirmation')
    def assert_passwords_match_or_are_both_none(cls, password_confirmation, values):
        password = values.get('password')
        if None not in [password, password_confirmation]:
            if not password == password_confirmation:
                raise ValueError("Passwords do not match.")
            if len(password_confirmation) < 1:
                # TODO use more robust password rules
                raise ValueError("Password must not be empty.")
        return password_confirmation


class User(AbstractUser):
    """Schema for user info displaying."""

    id: int
    created: datetime
    updated: datetime

    @validator('id')
    def assert_id_is_valid(cls, id):
        if not is_valid_id(id):
            raise ValueError("ID is invalid.")
        return id

    class Config:
        orm_mode = True
