# Copyright 2022 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/LGPL).

from typing import TYPE_CHECKING, Annotated, Any

from odoo.api import Environment
from odoo.exceptions import AccessDenied

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

from fastapi import Depends, Header, HTTPException, Query, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials

from .context import odoo_env_ctx
from .schemas import Paging

if TYPE_CHECKING:
    from .models.fastapi_endpoint import FastapiEndpoint
else:
    FastapiEndpoint = Any


def company_id() -> int | None:
    """This method may be overriden by the FastAPI app to set the allowed company
    in the Odoo env of the endpoint. By default, the company defined on the
    endpoint record is used.
    """
    return None


def odoo_env(company_id: Annotated[int | None, Depends(company_id)]) -> Environment:
    env = odoo_env_ctx.get()
    if company_id is not None:
        env = env(context=dict(env.context, allowed_company_ids=[company_id]))

    yield env


def authenticated_partner_impl() -> Partner:
    """This method has to be overriden when you create your fastapi app
    to declare the way your partner will be provided. In some case, this
    partner will come from the authentication mechanism (ex jwt token) in other cases
    it could comme from a lookup on an email received into an HTTP header ...
    See the fastapi_endpoint_demo for an example"""


def optionally_authenticated_partner_impl() -> Partner | None:
    """This method has to be overriden when you create your fastapi app
    and you need to get an optional authenticated partner into your endpoint.
    """


def authenticated_partner_env(
    partner: Annotated[Partner, Depends(authenticated_partner_impl)],
) -> Environment:
    """Return an environment with the authenticated partner id in the context"""
    return partner.with_context(authenticated_partner_id=partner.id).env


def optionally_authenticated_partner_env(
    partner: Annotated[Partner | None, Depends(optionally_authenticated_partner_impl)],
    env: Annotated[Environment, Depends(odoo_env)],
) -> Environment:
    """Return an environment with the authenticated partner id in the context if
    the partner is not None
    """
    if partner:
        return partner.with_context(authenticated_partner_id=partner.id).env
    return env


def authenticated_partner(
    partner: Annotated[Partner, Depends(authenticated_partner_impl)],
    partner_env: Annotated[Environment, Depends(authenticated_partner_env)],
) -> Partner:
    """If you need to get access to the authenticated partner into your
    endpoint, you can add a dependency into the endpoint definition on this
    method.
    This method is a safe way to declare a dependency without requiring a
    specific implementation. It depends on `authenticated_partner_impl`. The
    concrete implementation of authenticated_partner_impl has to be provided
    when the FastAPI app is created.
    This method return a partner into the authenticated_partner_env
    """
    return partner_env["res.partner"].browse(partner.id)


def optionally_authenticated_partner(
    partner: Annotated[Partner | None, Depends(optionally_authenticated_partner_impl)],
    partner_env: Annotated[Environment, Depends(optionally_authenticated_partner_env)],
) -> Partner | None:
    """If you need to get access to the authenticated partner if the call is
    authenticated, you can add a dependency into the endpoint definition on this
    method.

    This method defer from authenticated_partner by the fact that it returns
    None if the partner is not authenticated .
    """
    if partner:
        return partner_env["res.partner"].browse(partner.id)
    return None


def paging(
    page: Annotated[int, Query(ge=1)] = 1, page_size: Annotated[int, Query(ge=1)] = 80
) -> Paging:
    """Return a Paging object from the page and page_size parameters"""
    return Paging(limit=page_size, offset=(page - 1) * page_size)


def basic_auth_user(
    credential: Annotated[HTTPBasicCredentials, Depends(HTTPBasic())],
    env: Annotated[Environment, Depends(odoo_env)],
) -> Users:
    username = credential.username
    password = credential.password
    try:
        response = (
            env["res.users"]
            .sudo()
            .authenticate(
                db=env.cr.dbname,
                credential={
                    "type": "password",
                    "login": username,
                    "password": password,
                },
                user_agent_env=None,
            )
        )
        return env["res.users"].browse(response.get("uid"))
    except AccessDenied as ad:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Basic"},
        ) from ad


def authenticated_partner_from_basic_auth_user(
    user: Annotated[Users, Depends(basic_auth_user)],
    env: Annotated[Environment, Depends(odoo_env)],
) -> Partner:
    return env["res.partner"].browse(user.sudo().partner_id.id)


def fastapi_endpoint_id() -> int:
    """This method is overriden by the FastAPI app to make the fastapi.endpoint record
    available for your endpoint method. To get the fastapi.endpoint record
    in your method, you just need to add a dependency on the fastapi_endpoint method
    defined below
    """


def fastapi_endpoint(
    _id: Annotated[int, Depends(fastapi_endpoint_id)],
    env: Annotated[Environment, Depends(odoo_env)],
) -> FastapiEndpoint:
    """Return the fastapi.endpoint record"""
    return env["fastapi.endpoint"].browse(_id)


def accept_language(
    accept_language: Annotated[
        str | None,
        Header(
            alias="Accept-Language",
            description="The Accept-Language header is used to specify the language "
            "of the content to be returned. If a language is not available, the "
            "server will return the content in the default language.",
        ),
    ] = None,
) -> str:
    """This dependency is used at application level to document the way the language
    to use for the response is specified. The header is processed outside of the
    fastapi app to initialize the odoo environment with the right language.
    """
    return accept_language
