# Copyright 2023 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/LGPL).
"""
The demo router is a router that demonstrates how to use the fastapi
integration with odoo.
"""

from typing import Annotated

from psycopg2 import errorcodes
from psycopg2.errors import SerializationFailure

from odoo.api import Environment
from odoo.exceptions import AccessError, MissingError, UserError, ValidationError
from odoo.service.model import MAX_TRIES_ON_CONCURRENCY_FAILURE

from odoo.addons.base.models.res_partner import Partner

from fastapi import APIRouter, Depends, File, HTTPException, Query, status
from fastapi.responses import JSONResponse

from ..dependencies import authenticated_partner, fastapi_endpoint, odoo_env
from ..models import FastapiEndpoint
from ..schemas import DemoEndpointAppInfo, DemoExceptionType, DemoUserInfo

router = APIRouter(tags=["demo"])


@router.get("/demo")
async def hello_word():
    """Hello World!"""
    return {"Hello": "World"}


@router.get("/demo/exception")
async def exception(exception_type: DemoExceptionType, error_message: str):
    """Raise an exception

    This method is used in the test suite to check that any exception
    is correctly handled by the fastapi endpoint and that the transaction
    is roll backed.
    """
    exception_classes = {
        DemoExceptionType.user_error: UserError,
        DemoExceptionType.validation_error: ValidationError,
        DemoExceptionType.access_error: AccessError,
        DemoExceptionType.missing_error: MissingError,
        DemoExceptionType.http_exception: HTTPException,
        DemoExceptionType.bare_exception: NotImplementedError,
    }
    exception_cls = exception_classes[exception_type]
    if exception_cls is HTTPException:
        raise exception_cls(status_code=status.HTTP_409_CONFLICT, detail=error_message)
    raise exception_classes[exception_type](error_message)


@router.get("/demo/lang")
async def get_lang(env: Annotated[Environment, Depends(odoo_env)]):
    """Returns the language according to the available languages in Odoo and the
    Accept-Language header.

    This method is used in the test suite to check that the language is correctly
    set in the Odoo environment according to the Accept-Language header
    """
    return env.context.get("lang")


@router.get("/demo/who_ami")
async def who_ami(
    partner: Annotated[Partner, Depends(authenticated_partner)],
) -> DemoUserInfo:
    """Who am I?

    Returns the authenticated partner
    """
    # This method show you how you can rget the authenticated partner without
    # depending on a specific implementation.
    return DemoUserInfo(name=partner.name, display_name=partner.display_name)


@router.get(
    "/demo/endpoint_app_info",
    dependencies=[Depends(authenticated_partner)],
)
async def endpoint_app_info(
    endpoint: Annotated[FastapiEndpoint, Depends(fastapi_endpoint)],
) -> DemoEndpointAppInfo:
    """Returns the current endpoint configuration"""
    # This method show you how to get access to current endpoint configuration
    # It also show you how you can specify a dependency to force the security
    # even if the method doesn't require the authenticated partner as parameter
    return DemoEndpointAppInfo.model_validate(endpoint)


_CPT = 0


@router.get("/demo/retrying")
async def retrying(
    nbr_retries: Annotated[int, Query(gt=1, lt=MAX_TRIES_ON_CONCURRENCY_FAILURE)],
) -> int:
    """This method is used in the test suite to check that the retrying
    functionality in case of concurrency error on the database is working
    correctly for retryable exceptions.

    The output will be the number of retries that have been done.

    This method is mainly used to test the retrying functionality
    """
    global _CPT
    if _CPT < nbr_retries:
        _CPT += 1
        raise FakeConcurrentUpdateError("fake error")
    tryno = _CPT
    _CPT = 0
    return tryno


@router.post("/demo/retrying")
async def retrying_post(
    nbr_retries: Annotated[int, Query(gt=1, lt=MAX_TRIES_ON_CONCURRENCY_FAILURE)],
    file: Annotated[bytes, File()],
) -> JSONResponse:
    """This method is used in the test suite to check that the retrying
    functionality in case of concurrency error on the database is working
    correctly for retryable exceptions.

    The output will be the number of retries that have been done.

    This method is mainly used to test the retrying functionality
    """
    global _CPT
    if _CPT < nbr_retries:
        _CPT += 1
        raise FakeConcurrentUpdateError("fake error")
    tryno = _CPT
    _CPT = 0
    return JSONResponse(content={"retries": tryno, "file": file.decode("utf-8")})


class FakeConcurrentUpdateError(SerializationFailure):
    @property
    def pgcode(self):
        return errorcodes.SERIALIZATION_FAILURE
