from typing import TYPE_CHECKING, Any, Union
from .schemas import ContactInput, TimesheetInput, DateString
from typing import Dict

if TYPE_CHECKING:
    from .client import EzeasClient


class ContactAPI:
    def __init__(self, client: "EzeasClient") -> None:
        self._client = client
        self._path = "/api/client/v1/contact"

    async def create(
        self,
        data: Union[ContactInput, Dict[str, Any]],
    ) -> Any:
        """
        Create a new contact for the account associated with this client.

        The payload is expected to follow ContactInput, but any plain dict
        with equivalent keys is accepted.
        """
        return await self._client._request(
            "POST",
            self._path,
            json_body=data,
        )

    async def update(
        self,
        data: Union[ContactInput, Dict[str, Any]],
    ) -> Any:
        """
        Update an existing contact matched by external_id in the payload.
        Fields not provided are left unchanged by the backend.
        """
        return await self._client._request(
            "PUT",
            self._path,
            json_body=data,
        )

    async def delete(self, external_id: str) -> Any:
        """
        Delete an existing contact matched by external_id.

        The backend is expected to treat this as idempotent: deleting a
        non-existent contact should still return a success response shape.
        """
        return await self._client._request(
            "DELETE",
            self._path,
            json_body={"external_id": external_id},
        )

    async def get(self, external_id: str) -> Any:
        """
        Retrieve a single contact matched by external_id.

        The exact response shape is defined by the backend.
        """
        return await self._client._request(
            "GET",
            self._path,
            json_body={"external_id": external_id},
        )


class TimesheetAPI:
    def __init__(self, client: "EzeasClient") -> None:
        self._client = client
        self._path = "/api/client/v1/timesheet"

    async def create(
        self,
        data: Union[TimesheetInput, Dict[str, Any]],
    ) -> Any:
        """
        Create a timesheet for the account associated with this client.

        The payload is expected to follow TimesheetInput, but any plain dict
        with equivalent keys is accepted.
        """
        return await self._client._request(
            "POST",
            self._path,
            json_body=data,
        )

    async def update(
        self,
        data: Union[TimesheetInput, Dict[str, Any]],
    ) -> Any:
        """
        Update an existing timesheet.

        The backend is expected to match entries by job_code and date and
        overwrite the stored hour buckets using the values in
        timesheet_entries.
        """
        return await self._client._request(
            "PUT",
            self._path,
            json_body=data,
        )

    async def delete(self, job_code: str, date: DateString) -> Any:
        """
        Delete a timesheet entry matched by job_code and date.

        The backend should treat this as idempotent.
        """
        return await self._client._request(
            "DELETE",
            self._path,
            json_body={"job_code": job_code, "date": date},
        )

    async def get(self, job_code: str, date: DateString) -> Any:
        """
        Retrieve a timesheet entry matched by job_code and date.
        """
        return await self._client._request(
            "GET",
            self._path,
            json_body={"job_code": job_code, "date": date},
        )
