from typing import TYPE_CHECKING, Any, List, Optional, 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]],
        request_id: Optional[str] = None,
    ) -> 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,
            request_id=request_id,
        )

    async def update(
        self,
        data: Union[ContactInput, Dict[str, Any]],
        request_id: Optional[str] = None,
    ) -> 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,
            request_id=request_id,
        )

    async def delete(
        self,
        external_id: str,
        request_id: Optional[str] = None,
    ) -> 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},
            request_id=request_id,
        )

    async def get(
        self,
        external_id: str,
        request_id: Optional[str] = None,
    ) -> 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},
            request_id=request_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],
            List[Union[TimesheetInput, Dict[str, Any]]],
        ],
        request_id: Optional[str] = None,
    ) -> Any:
        """
        Create one or more timesheets for the account associated with this client.

        The payload may be:
        - a single TimesheetInput or plain dict, or
        - a list of TimesheetInput / dict objects.

        The SDK always normalizes this into a list before sending to the API.
        """
        if isinstance(data, list):
            payload = data
        else:
            payload = [data]

        return await self._client._request(
            "POST",
            self._path,
            json_body=payload,
            request_id=request_id,
        )

    async def update(
        self,
        data: Union[
            TimesheetInput,
            Dict[str, Any],
            List[Union[TimesheetInput, Dict[str, Any]]],
        ],
        request_id: Optional[str] = None,
    ) -> Any:
        """
        Update one or more existing timesheets.

        The backend is expected to match entries by job_code and date and
        overwrite the stored hour buckets using the values in timesheet_entries.

        The payload may be:
        - a single TimesheetInput or plain dict, or
        - a list of TimesheetInput / dict objects.

        The SDK always normalizes this into a list before sending to the API.
        """
        if isinstance(data, list):
            payload = data
        else:
            payload = [data]

        return await self._client._request(
            "PUT",
            self._path,
            json_body=payload,
            request_id=request_id,
        )

    async def delete(
        self,
        job_code: str,
        date: DateString,
        request_id: Optional[str] = None,
    ) -> 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},
            request_id=request_id,
        )

    async def get(
        self,
        job_code: str,
        date: DateString,
        request_id: Optional[str] = None,
    ) -> 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},
            request_id=request_id,
        )
