from http import HTTPStatus
from uuid import uuid4

from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import (
    BadRequestError,
    NotFoundError,
)
from layercake.dateutils import now
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
from layercake.extra_types import CnpjStr, CpfStr, NameStr
from pydantic import (
    UUID4,
    BaseModel,
    ConfigDict,
    EmailStr,
    Field,
)

from api_gateway import JSONResponse
from boto3clients import dynamodb_client
from config import USER_TABLE

router = Router()
logger = Logger(__name__)
layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)


class Org(BaseModel):
    id: str
    name: str
    cnpj: CnpjStr


class User(BaseModel):
    model_config = ConfigDict(arbitrary_types_allowed=True)

    id: UUID4 = Field(default_factory=uuid4)
    name: NameStr
    email: EmailStr
    email_verified: bool = False
    cpf: CpfStr
    org: Org = Field(..., exclude=True)


class UserNotFoundError(NotFoundError):
    def __init__(self, *_):
        super().__init__('User not found')


class CPFConflictError(BadRequestError):
    def __init__(self, *_):
        super().__init__('CPF already exists')


class EmailConflictError(BadRequestError):
    def __init__(self, *_):
        super().__init__('Email already exists')


@router.post('/', compress=True)
def add(user: User):
    now_ = now()
    user_id = user.id
    org = user.org

    try:
        with layer.transact_writer() as transact:
            transact.put(
                item={
                    'id': 'cpf',
                    'sk': user.cpf,
                    'user_id': user.id,
                    'created_at': now_,
                },
                cond_expr='attribute_not_exists(sk)',
                exc_cls=CPFConflictError,
            )

            transact.put(
                item={
                    'id': 'email',
                    'sk': user.email,
                    'user_id': user.id,
                    'created_at': now_,
                },
                cond_expr='attribute_not_exists(sk)',
                exc_cls=EmailConflictError,
            )
            transact.put(
                item={
                    'sk': '0',
                    'tenant_id': {org.id},
                    'email_verified': False,
                    # Post-migration: uncomment the following line
                    # 'createDate': now_,
                    'createDate': now_,
                }
                | user.model_dump()
            )
            transact.put(
                item={
                    'id': user_id,
                    'sk': f'emails#{user.email}',
                    'email_verified': False,
                    'email_primary': True,
                    'created_at': now_,
                }
            )
            transact.put(
                item={
                    'id': user.id,
                    'sk': f'orgs#{user.org.id}',
                    'name': org.name,
                    'cnpj': org.cnpj,
                    'created_at': now_,
                }
            )
            transact.put(
                item={
                    'id': f'orgmembers#{org.id}',
                    'sk': user.id,
                    'created_at': now_,
                }
            )

    except (CPFConflictError, EmailConflictError):
        user_id = layer.collection.get_items(
            KeyPair(
                pk='cpf',
                sk=SortKey(user.cpf, path_spec='user_id'),
                rename_key='id',
            )
            + KeyPair(
                pk='email',
                sk=SortKey(user.email, path_spec='user_id'),
                rename_key='id',
            ),
            flatten_top=False,
        ).get('id')

        if not user_id:
            raise UserNotFoundError()

        with layer.transact_writer() as transact:
            transact.update(
                key=KeyPair(user_id, '0'),
                update_expr='ADD tenant_id :org_id',
                expr_attr_values={
                    ':org_id': {org.id},
                },
            )
            transact.put(
                item={
                    'id': user_id,
                    'sk': f'orgs#{user.org.id}',
                    'name': org.name,
                    'cnpj': org.cnpj,
                    'created_at': now_,
                }
            )
            transact.put(
                item={
                    'id': f'orgmembers#{org.id}',
                    'sk': user_id,
                    'created_at': now_,
                }
            )

    return JSONResponse(
        status_code=HTTPStatus.CREATED,
        body={'id': user_id},
    )
