#
# # Copyright © 2026 Peak AI Limited. or its affiliates. All Rights Reserved.
# #
# # Licensed under the Apache License, Version 2.0 (the "License"). You
# # may not use this file except in compliance with the License. A copy of
# # the License is located at:
# #
# # https://github.com/PeakBI/peak-sdk/blob/main/LICENSE
# #
# # or in the "license" file accompanying this file. This file is
# # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# # ANY KIND, either express or implied. See the License for the specific
# # language governing permissions and limitations under the License.
# #
# # This file is part of the peak-sdk.
# # see (https://github.com/PeakBI/peak-sdk)
# #
# # You should have received a copy of the APACHE LICENSE, VERSION 2.0
# # along with this program. If not, see <https://apache.org/licenses/LICENSE-2.0>
#
"""Utility functions for delete operations."""

from __future__ import annotations

from typing import Any

from peak.exceptions import ForbiddenException
from peak.tools.logging import LogLevel, PeakLogger, get_logger

logger: PeakLogger = get_logger(level=LogLevel.INFO)


def validate_deployment_name(deployment_name: str) -> str | None:
    """Validate deployment name input parameter.

    Returns:
        Error message if validation fails, None if valid
    """
    if not deployment_name or not isinstance(deployment_name, str):
        return "deployment_name must be a non-empty string"
    return None


def is_valid_email(email: str) -> bool:
    """Check if a string looks like an email address."""
    if not email or "@" not in email:
        return False
    local_part, _, domain = email.partition("@")
    return bool(local_part and domain and "." in domain)


def get_spec_id(
    client: Any,
    deployment_name: str | None = None,
    suffix: str | None = None,
) -> str | None:
    """Retrieve the App or Block Spec ID based on the app name, optional suffix.

    Requires a valid email address in the createdBy field.

    Args:
        client: The client instance for app operations.
        deployment_name (str): The name of the app or block to find.
        suffix (str, optional): Optional suffix to filter the app or block spec.

    Returns:
        str: The App Spec ID if found, otherwise None.
    """
    specs = client.list_specs()  # Fetch all app specs

    matching_ids = [
        spec["id"]
        for spec in specs
        if (
            (
                (suffix is None and spec.get("metadata", {}).get("name", "") == deployment_name)
                or (
                    suffix is not None
                    and spec.get("metadata", {}).get("name", "").startswith(deployment_name)
                    and spec.get("metadata", {}).get("name", "").endswith(suffix)
                )
            )
            and is_valid_email(spec.get("metadata", {}).get("createdBy", ""))
        )
    ]

    if len(matching_ids) == 0:
        # Log warning if no match found
        msg = f"No app spec found starting with name '{deployment_name}'"
        if suffix:
            msg += f" and ending with '{suffix}'"
            logger.warning(msg)
            return None
    return matching_ids[0] if matching_ids else None


def get_block_spec_info(spec_id: str, client: Any, *, is_app: bool = True) -> dict[str, Any]:
    """Retrieve spec information for the given app or block spec ID.

    Args:
        spec_id: The spec ID to retrieve information for
        client: The client instance to use for API calls
        is_app: Whether the spec is an app or block

    Returns:
        The spec configuration

    Raises:
        ValueError: If retrieval fails or response format is invalid
    """
    try:
        spec_info = client.describe_spec(spec_id)
    except ValueError as e:
        msg = f"Failed to retrieve block spec info for {spec_id}: {e!s}"
        logger.exception(msg)
        raise
    except KeyError as e:
        msg = f"Missing required fields in spec info for {spec_id}"
        logger.exception("%s", msg)
        raise ValueError(msg) from e

    if is_app:
        if not spec_info.get("latestRelease", {}).get("config"):
            msg = f"Invalid spec info format for {spec_id}"
            logger.error(msg)
            raise ValueError(msg)
        return dict(spec_info["latestRelease"]["config"])
    return dict(spec_info)


def check_deployment(spec_id: str, client: Any) -> str | None:
    """Check if an app with the given spec ID has an existing deployment.

    Args:
        spec_id (str): The App Spec ID.
        client: The client instance for deployment operations.

    Returns:
        Optional[str]: The deployment ID if found, otherwise None.

    Raises:
        ValueError: If listing deployments fails.
    """
    try:
        deployed = client.list_deployments()
        matching = [item for item in deployed if item.get("spec", {}).get("id") == spec_id]
        return matching[0].get("id") if matching else None
    except ValueError as e:
        msg = f"Failed to list deployments: {e}"
        raise ValueError(msg) from e


def delete_deployment(spec_id: str, client: Any) -> None:
    """Handle the deletion of a deployment.

    Args:
        spec_id (str): The ID of the deployment to delete.
        client: The client instance for app operations.

    Raises:
        RuntimeError: If deployment is in an invalid state.
        ValueError: If deployment deletion fails.
        KeyError: If deployment metadata is malformed.
    """
    # Deployment status constants
    deploying = "deploying"
    deployed = "deployed"
    updating = "updating"
    failed = "failed"

    deployment_id = check_deployment(spec_id=spec_id, client=client)

    if not deployment_id:
        logger.info("No deployment found for spec %s, skipping resource deletion.", spec_id)
        return
    try:
        ans = client.describe_deployment(deployment_id)
        status = ans.get("metadata", {}).get("status")

        if status in (deploying, updating):
            msg = "Spec is currently deploying. Please try again once deployment is complete."
            logger.warning(msg)
            raise RuntimeError(msg)

        if status in (deployed, failed):
            try:
                client.delete_deployment(deployment_id)
                logger.info("Successfully deleted deployment %s", deployment_id)
            except (ValueError, KeyError, RuntimeError) as e:
                msg = f"Failed to delete deployment {deployment_id}: {e!s}"
                logger.exception(msg)
                raise ValueError(msg) from e
    except ValueError as e:
        msg = f"Failed to check deployment status for {deployment_id}: {e!s}"
        logger.exception(msg)
        raise ValueError(msg) from e
    except KeyError as e:
        msg = f"Invalid deployment metadata format for {deployment_id}"
        logger.exception("%s", msg)
        raise KeyError(msg) from e


def delete_app_spec(app_spec_id: str, app_client: Any) -> None:
    """Delete the App Spec by its ID.

    Args:
        app_spec_id (str): The App Spec ID to delete.
        app_client (Any): The app client instance to use for API calls.

    Raises:
        ValueError: If deletion of the App Spec fails.
    """
    try:
        app_client.delete_spec(spec_id=app_spec_id)
        logger.info("Successfully deleted app spec %s", app_spec_id)
    except ValueError as e:
        msg = f"Failed to delete App Spec {app_spec_id}: {e!s}"
        logger.exception(msg)
        raise ValueError(msg) from e


def delete_block_specs(block_specs: Any, block_client: Any) -> None:
    """Delete block specs provided in the list or a single block spec.

    Args:
        block_specs (Any): A single block spec dict or a list of block spec dicts to delete.
            Each dict must contain an 'id' field.
        block_client (Any): The block client instance to use for API calls.

    Raises:
        ValueError: If deletion of any block spec fails with an unexpected error.
    """
    # If block_specs is a dict (single block), wrap it in a list
    if isinstance(block_specs, dict):
        block_specs = [block_specs]

    for block_info in block_specs:
        block_id = block_info.get("id")
        if not block_id:
            logger.warning("Skipping block spec with missing ID")
            continue

        try:
            block_client.delete_spec(spec_id=block_id)
            logger.info("Successfully deleted block spec %s", block_id)
        except (ValueError, ForbiddenException) as e:
            # Log but continue with other blocks on expected errors
            msg = f"Failed to delete block spec {block_id}: {e!s}"
            logger.exception(msg)
        except (KeyError, RuntimeError) as e:
            # Raise on unexpected errors
            msg = f"Unexpected error deleting block spec {block_id}: {e!s}"
            logger.exception(msg)
            raise ValueError(msg) from e


def delete_resource_specs(
    spec_id: str,
    deployment_name: str,
    app_client: Any,
    block_client: Any,
    *,
    is_app: bool = True,
) -> str | None:
    """Delete all associated press resources.

    Args:
        spec_id: Spec ID to delete
        deployment_name: Deployment name for logging
        app_client: App client instance
        block_client: Block client instance
        is_app: True for app resources, False for block resources

    Returns:
        Error message if deletion fails, None if successful
    """
    try:
        if is_app:
            block_spec_info = get_block_spec_info(spec_id, app_client, is_app=is_app)
        else:
            block_spec_info = get_block_spec_info(spec_id, block_client, is_app=is_app)

        if is_app:
            delete_app_spec(spec_id, app_client)

            logger.info("[%s] Cleaning up associated block specs", deployment_name)
            delete_block_specs(block_spec_info, block_client)
        else:
            logger.info("[%s] Deleting block specs %s", deployment_name, spec_id)
            delete_block_specs(block_spec_info, block_client)
    except ValueError as e:
        return f"Failed to delete press resources: {e!s}"

    return None


def is_app(spec: dict[str, Any]) -> bool:
    """Return True if the spec dict represents an app spec (kind == 'app')."""
    return spec.get("kind") == "app"
