"""
    This module is used to handle HTTP requests.
    Includes JuiceFS WebDAV upload interception for task output storage.
"""

import json
import os
from urllib.parse import urljoin

import aiohttp
from aiohttp import BasicAuth, ClientError
from aiohttp_retry import FibonacciRetry, RetryClient

from gmi_ieops.runpod.http_client import ClientSession
from gmi_ieops.runpod.serverless.modules.rp_logger import RunPodLogger

from .worker_state import WORKER_ID

JOB_DONE_URL_TEMPLATE = str(
    os.environ.get("RUNPOD_WEBHOOK_POST_OUTPUT", "JOB_DONE_URL")
)
JOB_DONE_URL = JOB_DONE_URL_TEMPLATE.replace("$RUNPOD_POD_ID", WORKER_ID)

JOB_STREAM_URL_TEMPLATE = str(
    os.environ.get("RUNPOD_WEBHOOK_POST_STREAM", "JOB_STREAM_URL")
)
JOB_STREAM_URL = JOB_STREAM_URL_TEMPLATE.replace("$RUNPOD_POD_ID", WORKER_ID)

# WebDAV upload config (injected by deployment_builder via Secret + envVars)
_WEBDAV_URL = os.environ.get("IEOPS_WEBDAV_URL", "")
_WEBDAV_USER = os.environ.get("IEOPS_WEBDAV_USER", "")
_WEBDAV_PASS = os.environ.get("IEOPS_WEBDAV_PASSWORD", "")
_OWNER_UID = os.environ.get("IEOPS_OWNER_UID", "")
_OWNER_OID = os.environ.get("IEOPS_OWNER_OID", "")

log = RunPodLogger()


# ---------------------------------------------------------------------------
# WebDAV output upload (intra-cluster, async)
# ---------------------------------------------------------------------------

async def _webdav_mkcol(session: aiohttp.ClientSession, base_url: str, auth: BasicAuth, path: str):
    """Create WebDAV directories recursively. Ignores 405/409 (already exists)."""
    parts = path.strip("/").split("/")
    for i in range(len(parts)):
        sub = "/".join(parts[:i + 1]) + "/"
        url = urljoin(base_url.rstrip("/") + "/", sub)
        async with session.request("MKCOL", url, auth=auth) as resp:
            if resp.status not in (201, 405, 409):
                log.warn(f"WebDAV MKCOL {sub} failed: {resp.status}")


async def _upload_output(job_id: str, job_data: dict) -> dict:
    """
    Upload task output to JuiceFS via WebDAV (intra-cluster).
    Returns modified job_data with file URL reference, or original on failure/skip.
    """
    if not _WEBDAV_URL or not _OWNER_OID:
        return job_data  # not configured, pass through

    # Only intercept final results with output, not errors or IN_PROGRESS
    if job_data.get("status") == "IN_PROGRESS":
        return job_data
    output = job_data.get("output")
    if output is None:
        return job_data

    content = json.dumps(job_data, ensure_ascii=False).encode("utf-8")
    dir_path = f"{_OWNER_OID}/{_OWNER_UID}/outputs"
    file_path = f"{dir_path}/output-{job_id}.json"
    auth = BasicAuth(_WEBDAV_USER, _WEBDAV_PASS)

    try:
        async with aiohttp.ClientSession() as session:
            await _webdav_mkcol(session, _WEBDAV_URL, auth, dir_path)
            put_url = urljoin(_WEBDAV_URL.rstrip("/") + "/", file_path)
            async with session.put(
                put_url,
                data=content,
                auth=auth,
                headers={"Content-Type": "application/json"},
            ) as resp:
                if resp.status in (200, 201, 204):
                    log.info(f"Output uploaded to WebDAV: {file_path}", job_id)
                    return {
                        "output": {
                            "_file_url": f"/api/v1/files/{file_path}",
                            "_file_uploaded": True,
                            "_original_size": len(content),
                        }
                    }
                else:
                    log.warn(f"WebDAV PUT failed: {resp.status}", job_id)
    except Exception as err:
        log.error(f"Output upload failed, sending original result. | {err}", job_id)

    return job_data  # fallback: return original


# ---------------------------------------------------------------------------
# HTTP result transmission
# ---------------------------------------------------------------------------

async def _transmit(client_session: ClientSession, url, job_data):
    """
    Wrapper for transmitting results via POST.
    """
    retry_options = FibonacciRetry(attempts=3)
    retry_client = RetryClient(
        client_session=client_session, retry_options=retry_options
    )

    kwargs = {
        "data": job_data,
        "headers": {
            "charset": "utf-8",
            "Content-Type": "application/x-www-form-urlencoded",
        },
        "raise_for_status": True,
    }

    async with retry_client.post(url, **kwargs) as client_response:
        await client_response.text()


async def _handle_result(
    session: ClientSession, job_data, job, url_template, log_message, is_stream=False
):
    """
    A helper function to handle the result, either for sending or streaming.
    """
    try:
        session.headers["X-Request-ID"] = job["id"]

        # Intercept final results: upload to JuiceFS WebDAV before sending to waverless
        if url_template == JOB_DONE_URL and not is_stream:
            job_data = await _upload_output(job["id"], job_data)

        serialized_job_data = json.dumps(job_data, ensure_ascii=False)

        is_stream = "true" if is_stream else "false"
        url = url_template.replace("$ID", job["id"]) + f"&isStream={is_stream}"

        await _transmit(session, url, serialized_job_data)
        log.debug(f"{log_message}", job["id"])

    except ClientError as err:
        log.error(f"Failed to return job results. | {err}", job["id"])

    except (TypeError, RuntimeError) as err:
        log.error(f"Error while returning job result. | {err}", job["id"])

    finally:
        # job_data status is used for local development with FastAPI
        if (
            url_template == JOB_DONE_URL
            and job_data.get("status", None) != "IN_PROGRESS"
        ):
            log.info("Finished.", job["id"])


async def send_result(session, job_data, job, is_stream=False):
    """
    Return the job results.
    """
    await _handle_result(
        session, job_data, job, JOB_DONE_URL, "Results sent.", is_stream=is_stream
    )


async def stream_result(session, job_data, job):
    """
    Return the stream job results.
    """
    await _handle_result(
        session, job_data, job, JOB_STREAM_URL, "Intermediate results sent."
    )
