#
# # 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>
#
"""Spec publishing utilities."""

from __future__ import annotations

import json
import os
from pathlib import Path
from typing import Any

import requests
from packaging import version as pkg_version

from peak.press import apps, blocks
from peak.template import load_template
from peak.tools.logging import LogLevel, PeakLogger, get_logger

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


def update_spec_metadata(client: Any, spec_id: str, template: dict[str, Any]) -> None:
    """Update spec metadata with error handling."""
    metadata = {
        "metadata": template["body"]["metadata"],
        "scope": template["scope"],
        "tenants": template.get("tenants"),
    }
    try:
        client.update_spec_metadata(spec_id, metadata)
    except (ValueError, KeyError, RuntimeError) as e:
        error_details = e.args[0] if len(e.args) > 0 and isinstance(e.args[0], dict) else {}
        if error_details.get("detail") == "No changes detected":
            logger.info("No changes detected in spec metadata, continuing for rest...")
        else:
            logger.exception("Failed to update spec metadata")
            raise


def load_spec_template(params: dict[str, Any], template_path: str) -> dict[str, Any]:
    """Load and render spec template."""
    template_file = Path(__file__).parent.parent.parent / "press" / template_path
    return load_template(file=template_file, params=params)


def publish_spec(
    existing_specs: list[dict[str, Any]],
    spec_name: str,
    version: str,
    template_path: str,
    params: dict[str, Any],
    *,
    is_app: bool,
) -> str:
    """Publish or update a spec with proper version and tenant handling."""
    try:
        spec_template = load_spec_template(params, template_path)
    except (ValueError, TypeError, OSError):
        logger.exception("Failed to load spec template")
        raise

    current_scope = spec_template["scope"]
    current_tenants = spec_template.get("tenants", [])

    # Find existing spec that matches name, scope, and tenants (if shared)
    existing_spec: dict[str, Any] | None = None
    for spec in existing_specs:
        if spec["metadata"]["name"] == spec_name and spec.get("scope") == current_scope:
            if current_scope == "shared":
                spec_tenants = spec.get("tenants", [])
                if sorted(spec_tenants) == sorted(current_tenants):
                    existing_spec = spec
                    break
            else:
                existing_spec = spec
                break

    client = apps.get_client() if is_app else blocks.get_client()

    if existing_spec:
        existing_version = existing_spec["latestRelease"]["version"]
        if existing_version == version:
            logger.info("%s %s exists. ID: %s", spec_name, version, existing_spec["id"])
            return str(existing_spec["id"])
        if pkg_version.parse(version) < pkg_version.parse(existing_version):
            logger.error(
                "Provided version %s is less than the existing version %s for spec %s",
                version,
                existing_version,
                spec_name,
            )
            msg = f"Cannot update {spec_name} to an older version {version} (current: {existing_version})"
            raise ValueError(msg)
        logger.info(
            "Updating spec: %s to version %s (previous: %s)",
            spec_name,
            version,
            existing_version,
        )
        return update_existing_spec(version, client, existing_spec["id"], spec_template, is_app=is_app)

    logger.info("Publishing spec: %s", spec_name)
    return str(create_new_spec(client, spec_template, is_app=is_app)["id"])


def update_existing_spec(
    version: str,
    client: Any,
    spec_id: str,
    template: dict[str, Any],
    *,
    is_app: bool,
) -> str:
    """Handle existing spec update with proper parameter filtering."""
    # Extract template body with explicit typing
    template_body: dict[str, Any] = template["body"]

    # Create the body dict first
    body_dict: dict[str, Any] = {k: v for k, v in template_body.items() if k not in {"version", "kind", "metadata"}}

    # Ensure release dict exists
    if "release" not in body_dict:
        body_dict["release"] = {}

    # Now safely set the version
    body_dict["release"]["version"] = version

    update_params: dict[str, Any] = {
        "spec_id": spec_id,
        "body": body_dict,
    }

    if not is_app:
        update_params["artifact"] = template["artifact"]
    if template.get("parameters"):
        update_params["parameters"] = template["parameters"]

    client.create_spec_release(**update_params)
    update_spec_metadata(client, spec_id, template)
    return spec_id


def create_new_spec(client: Any, template: dict[str, Any], *, is_app: bool) -> dict[str, Any]:
    """Create new spec with proper parameter handling."""
    # Extract values with explicit typing to help mypy
    body: dict[str, Any] = template["body"]
    scope: str = template["scope"]

    create_params: dict[str, Any] = {
        "body": body,
        "scope": scope,
    }

    if scope == "shared":
        tenants: list[str] = template.get("tenants", [])
        create_params["tenants"] = [t for t in tenants if t.strip()]

    parameters = template.get("parameters")
    if parameters:
        create_params["parameters"] = parameters

    if not is_app:
        create_params["artifact"] = template["artifact"]
    else:
        create_params["featured"] = True

    return dict(client.create_spec(**create_params))


def is_spec_exists(existing_specs: list[dict[str, Any]], spec_name: str) -> bool:
    """Check if the spec exists."""
    return any(spec["metadata"]["name"] == spec_name for spec in existing_specs)


def is_spec_version_exists(existing_specs: list[dict[str, Any]], spec_name: str, version: str) -> bool:
    """Check if the spec exists with same version."""
    return any(
        spec["metadata"]["name"] == spec_name and spec["latestRelease"]["version"] == version for spec in existing_specs
    )


def publish_data_model(spec_id: str, version: str) -> None:
    """Publish data model schema for the application."""
    input_file = Path("data_models/input-data-model.json")
    try:
        with input_file.open() as f:
            data = json.load(f)
            data["metadata"]["schema"] = os.environ.get("INPUT_SCHEMA", "")

        response = requests.post(
            f"https://press.peak.ai/v1/apps/data-model/{spec_id}/{version}/files",
            headers={"Authorization": os.environ["PEAK_AUTH_TOKEN"]},
            data={"type": "schema", "schemaType": "input"},
            files={"artifact": ("input-data-model.json", json.dumps(data))},
            timeout=10,
        )
        response.raise_for_status()
        logger.info("✅ Data model published successfully")
    except requests.exceptions.RequestException:
        logger.exception("❌ Failed to publish data model")
        raise
