import asyncio
from typing import Annotated

from arcade.sdk import ToolContext, tool
from arcade.sdk.auth import OAuth2
from arcade.sdk.errors import ToolExecutionError

from arcade_salesforce.enums import SalesforceObject
from arcade_salesforce.models import SalesforceClient
from arcade_salesforce.utils import clean_account_data


# TODO: We only return up to 10 items of each related object (e.g. contacts). Need to implement
# separate tools for each related object, so that we can return more items, when needed.
@tool(
    requires_auth=OAuth2(
        id="salesforce",
        scopes=[
            "read_account",
            "read_contact",
            "read_lead",
            "read_note",
            "read_opportunity",
            "read_task",
        ],
    )
)
async def get_account_data_by_keywords(
    context: ToolContext,
    query: Annotated[
        str,
        "The query to search for accounts. MUST be longer than one character. It will match the "
        "keywords against all account fields, such as name, website, phone, address, etc. "
        "E.g. 'Acme'",
    ],
    # Note: Salesforce supports up to 200 results, but since we're enriching each account with
    # related objects, we limit to 10, so that the response is not too lengthy for LLMs.
    limit: Annotated[
        int,
        "The maximum number of accounts to return. Defaults to 10. Maximum allowed is 10.",
    ] = 10,
    page: Annotated[int, "The page number to return. Defaults to 1 (first page of results)."] = 1,
) -> Annotated[
    dict,
    "The accounts matching the query with related info: contacts, leads, notes, calls, "
    "opportunities, tasks, emails, and events (up to 10 items of each type)",
]:
    """Searches for accounts in Salesforce and returns them with related info: contacts, leads,
    notes, calls, opportunities, tasks, emails, and events (up to 10 items of each type).

    An account is an organization (such as a customer, supplier, or partner, though more commonly
    a customer). In some Salesforce account setups, an account can also represent a person.
    """
    if len(query) < 2:
        raise ToolExecutionError("The `query` argument must have two or more characters.")

    limit = min(limit, 10)

    client = SalesforceClient(context.get_auth_token_or_empty())

    params = {
        "q": query,
        "sobjects": [
            {
                "name": "Account",
                "fields": await client.get_object_fields(SalesforceObject.ACCOUNT),
            }
        ],
        "in": "ALL",
        "overallLimit": limit,
        "offset": (page - 1) * limit,
    }
    response = await client.post("parameterizedSearch", json_data=params)
    search_results = response["searchRecords"]

    accounts = await asyncio.gather(*[
        client.enrich_account(
            account_data=account,
            limit_per_association=10,
        )
        for account in search_results
    ])
    return {"accounts": [clean_account_data(account) for account in accounts]}


@tool(
    requires_auth=OAuth2(
        id="salesforce",
        scopes=[
            "read_account",
            "read_contact",
            "read_lead",
            "read_note",
            "read_opportunity",
            "read_task",
        ],
    )
)
async def get_account_data_by_id(
    context: ToolContext,
    account_id: Annotated[
        str,
        "The ID of the account to get data for.",
    ],
) -> Annotated[
    dict,
    "The account with related info: contacts, leads, notes, calls, opportunities, tasks, emails, "
    "and events (up to 10 items of each type)",
]:
    """Gets the account with related info: contacts, leads, notes, calls, opportunities, tasks,
    emails, and events (up to 10 items of each type).

    An account is an organization (such as a customer, supplier, or partner, though more commonly
    a customer). In some Salesforce account setups, an account can also represent a person.
    """
    client = SalesforceClient(context.get_auth_token_or_empty())

    account = await client.get_account(account_id)

    if not account:
        return {"account": None, "error": f"No account found with id '{account_id}'"}

    account = await client.enrich_account(account_data=account)
    account = clean_account_data(account)

    return {"account": account}
