from typing import TYPE_CHECKING

from requests import Response

from mstrio.utils.api_helpers import changeset_manager
from mstrio.utils.error_handlers import ErrorHandler

if TYPE_CHECKING:
    from mstrio.connection import Connection


@ErrorHandler(
    err_msg="Selected project {name} does not exist or is not loaded."
    " Please load the project or select a valid project "
    "or create a new project using `create_new` method"
)
def get_project(
    connection: 'Connection',
    name: str,
    error_msg: str | None = None,
    throw_error: bool = True,
    whitelist: list = [('ERR001', 500), ('ERR014', 403)],  # noqa: B006
) -> Response:
    """Get a specific project that the authenticated user has access to.

    Args:
        connection (Connection): Strategy One REST API connection object
        name (string): Strategy One project name
        error_msg (string, optional): Custom Error Message for Error Handling
        whitelist(list, optional): list of tuples of I-Server Error and
            HTTP errors codes respectively, which will not be handled
            i.e. whitelist = [('ERR001', 500),('ERR004', 404)]

    Returns:
        Complete HTTP response object.
    """
    return connection.get(endpoint=f'/api/projects/{name}')


@ErrorHandler(err_msg="Error fetching list of available projects.")
def get_projects(
    connection: 'Connection',
    error_msg: str | None = None,
    whitelist: list | None = None,
) -> Response:
    """Get a list of all projects that the authenticated user has access to.

    Args:
        connection (Connection): Strategy One REST API connection object
        error_msg (string, optional): Custom Error Message for Error Handling
        whitelist(list, optional): list of tuples of I-Server Error and
            HTTP errors codes respectively, which will not be handled
            i.e. whitelist = [('ERR001', 500),('ERR004', 404)]

    Returns:
        Complete HTTP response object.
    """

    return connection.get(endpoint='/api/projects', headers={'X-MSTR-ProjectID': None})


@ErrorHandler(err_msg="Error creating a new project.")
def create_project(
    connection: 'Connection',
    name: str,
    description: str | None = None,
    error_msg: str | None = None,
) -> Response:
    """Create a new project, either synchronously or asynchronously.

    Args:
        connection (Connection): Strategy One REST API connection object
        name (string): Name of the new project
        description (string, optional): Description of the new project
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    body = {'name': name, 'description': description}
    return connection.post(
        endpoint='/api/v2/projects',
        headers={'X-MSTR-ProjectID': None},
        json=body,
    )


@ErrorHandler(err_msg="Error getting project import quota for project with ID {id}")
def get_project_import_quota(
    connection: 'Connection', id: str, error_msg: str | None = None
) -> Response:
    """Get the amount of space, in MB, that can be used for the Data Import
    function for a specific project. This is the default value applied to all
    users.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project id string
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.get(endpoint=f'/api/projects/{id}/quotas')


@ErrorHandler(err_msg="Error setting import quota for project with ID {id}")
def set_project_import_quota(
    connection: 'Connection', id: str, body: dict, error_msg: str | None = None
) -> Response:
    """Set the amount of space, in MB, that can be used for the Data Import
    function for a specific project.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project id string
        body (dict): JSON-formatted definition of the dataset. Generated by
            `utils.formjson()`.
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.put(
        endpoint=f'/api/projects/{id}/quotas',
        headers={'X-MSTR-ProjectID': None},
        json=body,
    )


@ErrorHandler(
    err_msg="Error setting user {user_id} import quota for project with ID {id}"
)
def set_user_import_quota(
    connection: 'Connection',
    id: str,
    user_id: str,
    body: dict,
    error_msg: str | None = None,
) -> Response:
    """Set the amount of space, in MB, that can be used for the Data Import
    function for a specific user. The value provided is rounded to an integer.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project id string
        user_id (string): User ID string
        body (dict): JSON-formatted definition of the dataset. Generated by
            `utils.formjson()`.
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.put(
        endpoint=f'/api/projects/{id}/users/{user_id}/quotas',
        headers={'X-MSTR-ProjectID': None},
        json=body,
    )


@ErrorHandler(err_msg="Error getting user import quota for project with ID {id}")
def get_user_import_quota(
    connection: 'Connection',
    id: str,
    user_id: str | None = None,
    error_msg: str | None = None,
) -> Response:
    """Get a list of users for whom Data Import quotas have been set and their
    quotas.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project id string
        user_id (string, optional): User id string
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.get(
        endpoint=f'/api/projects/{id}/users/quotas',
        headers={'X-MSTR-ProjectID': None},
        params={'user_id': user_id},
    )


@ErrorHandler(err_msg="Error fetching project {id} settings configuration.")
def get_project_settings_config(
    connection: 'Connection', id: str, error_msg: str | None = None
) -> Response:
    """Get project settings configurations.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.get(
        endpoint=f'/api/v2/projects/{id}/settings/config',
        headers={'X-MSTR-ProjectID': None},
    )


@ErrorHandler(err_msg="Error getting project settings for project with ID {id}")
def get_project_settings(
    connection: 'Connection',
    id: str,
    error_msg: str | None = None,
    whitelist: list | None = None,
) -> Response:
    """Get project settings.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project id string
        error_msg (string, optional): Custom Error Message for Error Handling
        whitelist(list, optional): list of tuples of I-Server Error and
            HTTP errors codes respectively, which will not be handled
            i.e. whitelist = [('ERR001', 500),('ERR004', 404)]

    Returns:
        Complete HTTP response object.
    """
    return connection.get(
        endpoint=f'/api/v2/projects/{id}/settings',
        headers={'X-MSTR-ProjectID': None},
    )


@ErrorHandler(err_msg="Error setting project settings for project with ID {id}")
def set_project_settings(
    connection: 'Connection', id: str, body: dict, error_msg: str | None = None
) -> Response:
    """Set new project settings.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string, optional): Project id string
        body (dict): body of the request
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.put(
        endpoint=f'/api/v2/projects/{id}/settings',
        headers={'X-MSTR-ProjectID': None},
        json=body,
    )


@ErrorHandler(err_msg="Error updating project settings for project with ID {id}")
def update_project_settings(
    connection: 'Connection', id: str, body: dict, error_msg: str | None = None
) -> Response:
    """Update project settings.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project id string
        body (dict): body of the request
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.patch(
        endpoint=f'/api/v2/projects/{id}/settings',
        headers={'X-MSTR-ProjectID': None},
        json=body,
    )


@ErrorHandler(err_msg="Error getting engine setting for project with ID {id}")
def get_engine_settings(
    connection: 'Connection', id: str, error_msg: str | None = None
) -> Response:
    """Get available and current engine settings for a project.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project id string
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.get(
        endpoint=f'/api/projects/{id}/settings/engine',
        headers={'X-MSTR-ProjectID': None},
    )


@ErrorHandler(err_msg="Error getting project startup settings.")
def get_projects_on_startup(
    connection: 'Connection',
    error_msg: str | None = None,
    whitelist: list | None = None,
) -> Response:
    """Get a list of projects along with the nodes on which they would be
    available when the iServer starts up.

    Args:
        connection (Connection): Strategy One REST API connection object
        error_msg (string, optional): Custom Error Message for Error Handling
        whitelist(list, optional): list of tuples of I-Server Error and
            HTTP errors codes respectively, which will not be handled
            i.e. whitelist = [('ERR001', 500),('ERR004', 404)]

    Returns:
        Complete HTTP response object.
    """
    return connection.get(
        endpoint='/api/projects/settings/onStartup',
        headers={'X-MSTR-ProjectID': None},
    )


@ErrorHandler(err_msg="Error updating project startup settings.")
def update_projects_on_startup(
    connection: 'Connection',
    body: dict,
    error_msg: str | None = None,
    whitelist: list | None = None,
) -> Response:
    """Update status of projects on iServer nodes at start up. You provide
    the request body as of list of replace operations to be performed on the
    value of array of nodes with the path URI containing the corresponding
    project id that needs to be updated.

    Args:
        connection (Connection): Strategy One REST API connection object
        body (dict): JSON-formatted data used to update project startup settings
            {
            "operationList": [
                {
                    "op": "replace",
                    "path": "/projects/B7CA92F04B9FAE8D941C3E9B7E0CD754/nodes",
                    "value": ["env-183260laio2use1"],
                }
            ]
            }
        error_msg (string, optional): Custom Error Message for Error Handling
        whitelist(list, optional): list of tuples of I-Server Error and
            HTTP errors codes respectively, which will not be handled
            i.e. whitelist = [('ERR001', 500),('ERR004', 404)]

    Returns:
        Complete HTTP response object.
    """
    return connection.patch(
        endpoint='/api/projects/settings/onStartup',
        headers={'X-MSTR-ProjectID': None},
        json=body,
    )


@ErrorHandler(err_msg="Error getting VLDB settings for project with ID {id}")
def get_vldb_settings(
    connection: 'Connection', id: str, error_msg: str | None = None
) -> Response:
    """Get advanced VLDB settings for a project.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.get(
        endpoint=f'/api/model/projects/{id}?showAdvancedProperties=true',
        headers={'X-MSTR-ProjectID': id},
    )


@ErrorHandler(err_msg="Error updating VLDB settings for project with ID {id}")
def update_vldb_settings(
    connection: 'Connection', id: str, body: dict, error_msg: str | None = None
) -> Response:
    """Update metadata of advanced VLDB settings for a project.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        body (dict): JSON-formatted data used to update VLDB settings
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    with changeset_manager(connection) as changeset_id:
        return connection.put(
            endpoint=f'/api/model/projects/{id}',
            headers={'X-MSTR-MS-Changeset': changeset_id},
            json=body,
        )


@ErrorHandler(
    err_msg="Error getting metadata of VLDB settings for project with ID {id}"
)
def get_applicable_vldb_settings(
    connection: 'Connection', id: str, error_msg: str | None = None
) -> Response:
    """Get metadata of advanced VLDB settings for a project.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.get(
        endpoint=f'/api/model/projects/{id}/applicableAdvancedProperties'
    )


@ErrorHandler(err_msg="Error deleting project with ID {id}")
def delete_project(
    connection: 'Connection', id: str, error_msg: str | None = None
) -> Response:
    """Delete a project.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.delete(endpoint=f'/api/projects/{id}')


@ErrorHandler(err_msg="Error getting languages for project with ID {id}")
def get_project_languages(
    connection: 'Connection',
    id: str,
    fields: str | None = None,
    error_msg: str | None = None,
) -> Response:
    """Get languages for a specified project

    Args:
        connection (Connection): Strategy One connection object returned by
            `connection.Connection()`
        id (string): Project ID
        fields (string, optional): A whitelist of top-level fields separated by
            commas. Allow the client to selectively retrieve fields in the
            response.
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.get(
        endpoint=f'/api/projects/{id}/languages',
        params={'fields': fields},
    )


@ErrorHandler(err_msg="Error updating languages for project with ID {id}")
def update_project_languages(
    connection: 'Connection',
    id: str,
    body: dict,
    fields: str | None = None,
    error_msg: str | None = None,
) -> Response:
    """Update project language configurations.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        body (dict): JSON-formatted data used to update Project Languages
        fields (string, optional): A whitelist of top-level fields separated by
            commas. Allow the client to selectively retrieve fields in the
            response.
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.patch(
        endpoint=f'/api/projects/{id}/languages',
        json=body,
        params={'fields': fields},
    )


@ErrorHandler(err_msg="Error getting project lock status for project with ID {id}")
def get_project_lock(
    connection: 'Connection',
    id: str,
    fields: str | None = None,
    error_msg: str | None = None,
) -> Response:
    """Get the lock status of a project.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        fields (string, optional): A whitelist of top-level fields separated by
            commas. Allow the client to selectively retrieve fields in the
            response.
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.get(
        endpoint=f'/api/projects/{id}/lock', params={'fields': fields}
    )


@ErrorHandler(err_msg="Error updating project lock status for project with ID {id}")
def update_project_lock(
    connection: 'Connection', id: str, body: dict, error_msg: str | None = None
) -> Response:
    """Update the lock status of a project.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        body (dict): JSON-formatted data used to update Project Lock
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.put(endpoint=f'/api/projects/{id}/lock', json=body)


@ErrorHandler(err_msg="Error unlocking project lock for project with ID {id}")
def delete_project_lock(
    connection: 'Connection',
    id: str,
    lock_type: str,
    force: bool = False,
    lock_id: str | None = None,
    error_msg: str | None = None,
) -> Response:
    """Unlock a project.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        lock_type (str): The type of the lock to unlock
        force (bool, optional): Whether to force unlock the lock
        lock_id (str, optional): The id of the lock to unlock when
            not force unlock
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object.
    """
    return connection.delete(
        endpoint=f'/api/projects/{id}/lock',
        params={'lockType': lock_type, 'force': force, 'lockId': lock_id},
    )


@ErrorHandler(err_msg="Error updating project with ID {id}")
def update_project(
    connection: 'Connection',
    id: str,
    body: dict,
    fields: str | None = None,
) -> 'Response':
    """Update a project.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        body (dict): JSON-formatted data used to update Project
        fields (string, optional): A whitelist of top-level fields separated by
            commas. Allow the client to selectively retrieve fields in the
            response.

    Returns:
        Complete HTTP response object.
    """
    return connection.patch(
        endpoint=f'/api/projects/{id}',
        json=body,
        headers={
            'Content-Type': 'application/json-patch+json',
        },
        params={'fields': fields},
    )


@ErrorHandler(err_msg="Error getting project duplications")
def get_project_duplications(
    connection: 'Connection',
    offset: int = 0,
    limit: int = 100,
    error_msg: str | None = None,
) -> Response:
    """Get project duplications.

    Args:
        connection (Connection): Strategy One REST API connection object
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object. 200 on success.
    """
    return connection.get(endpoint='/api/projectDuplications')


@ErrorHandler(err_msg="Error triggering a new project duplication")
def trigger_project_duplication(
    connection: 'Connection',
    id: str,
    body: dict,
    prefer: str = 'respond-async',
    error_msg: str | None = None,
) -> Response:
    """Trigger a new project duplication.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project ID
        body (dict): Dictionary containing the project duplication settings
        prefer (str, optional): The preferred response mode. Defaults to
            'respond-async'
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object. 201 on success.
    """
    return connection.post(
        endpoint='/api/projectDuplications',
        json=body,
        headers={'X-MSTR-ProjectID': id, 'Prefer': prefer},
    )


@ErrorHandler(err_msg="Error getting project duplication with ID {id}")
def get_project_duplication(
    connection: 'Connection',
    id: str,
    error_msg: str | None = None,
) -> Response:
    """Get a specific project duplication.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project duplication ID
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        Complete HTTP response object. 200 on success.
    """
    return connection.get(endpoint=f'/api/projectDuplications/{id}')


@ErrorHandler(err_msg="Error cancelling project duplication with ID: {id}")
def cancel_project_duplication(
    connection: 'Connection',
    id: str,
    body: dict,
    error_msg: str | None = None,
) -> Response:
    """Cancel a specific project duplication.

    Args:
        connection (Connection): Strategy One REST API connection object
        id (string): Project duplication ID
        body (dict): Dictionary containing status update properties
        error_msg (string, optional): Custom Error Message for Error Handling

    Returns:
        HTTP response object. 204 on success.
    """
    return connection.put(endpoint=f'/api/projectDuplications/{id}/status', json=body)
