from datetime import datetime
from typing import Literal, NamedTuple, Protocol

from core.domain.api_key import APIKey, CompleteAPIKey
from core.domain.tenant_data import TenantData


class AutomaticPayment(NamedTuple):
    threshold: float
    balance_to_maintain: float


class TenantStorage(Protocol):
    async def tenant_by_uid(self, tenant_uid: int) -> TenantData: ...
    async def tenant_by_api_key(self, api_key: str) -> TenantData: ...
    async def tenant_by_org_id(self, org_id: str) -> TenantData: ...
    async def tenant_by_owner_id(self, owner_id: str) -> TenantData: ...

    async def create_tenant(self, tenant: TenantData) -> TenantData: ...
    async def create_tenant_for_org_id(self, org_id: str, org_slug: str | None, owner_id: str) -> TenantData: ...
    async def create_tenant_for_owner_id(self, owner_id: str) -> TenantData: ...

    async def update_tenant_slug(self, slug: str) -> TenantData: ...

    async def current_tenant(self) -> TenantData: ...

    async def create_api_key(self, name: str, created_by: str) -> CompleteAPIKey: ...
    async def list_api_keys(self) -> list[APIKey]: ...
    async def delete_api_key(self, api_key_id: str) -> None: ...
    async def update_api_key_last_used_at(self, api_key_id: str, last_used_at: datetime) -> None: ...

    async def decrement_credits(self, credits: float) -> TenantData: ...
    async def set_customer_id(self, customer_id: str) -> TenantData: ...
    async def clear_payment_failure(self): ...
    async def update_automatic_payment(self, automatic_payment: AutomaticPayment | None) -> None: ...
    async def attempt_lock_for_payment(self) -> TenantData | None: ...

    async def add_credits(self, credits: float) -> TenantData: ...
    async def unlock_payment_for_success(self, amount: float) -> None: ...
    async def unlock_payment_for_failure(
        self,
        now: datetime,
        code: Literal["internal", "payment_failed"],
        failure_reason: str,
    ): ...
    # Returns either a payment failure or None
    # or raise an error if the organization is locked
    async def check_unlocked_payment_failure(self) -> TenantData.PaymentFailure | None: ...

    async def lock_for_low_credits_email(self, threshold: float) -> bool:
        """Locks for sending a low credits email for a given threshold, or any threshold below it.
        Returns True if the lock was successful, False otherwise."""
        ...

    async def clear_low_credits_email(self, threshold: float) -> None:
        """Removes locks for sending a low credits email below a given threshold"""
        ...
