from __future__ import annotations

import base64
import hashlib
import hmac
import os
import time
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen

from services.storage.aliyun_oss_signed_upload import _require_text, _validate_object_key


class AliyunOssObjectDeleteService:
    storage_provider = "aliyun_oss"

    def __init__(
        self,
        *,
        endpoint: str,
        bucket: str,
        region: str,
        access_key_id: str,
        access_key_secret: str,
        now_epoch: int | None = None,
    ) -> None:
        _require_text(endpoint, "ALIYUN_OSS_ENDPOINT")
        _require_text(bucket, "ALIYUN_OSS_BUCKET")
        _require_text(region, "ALIYUN_OSS_REGION")
        _require_text(access_key_id, "ALIYUN_OSS_ACCESS_KEY_ID")
        _require_text(access_key_secret, "ALIYUN_OSS_ACCESS_KEY_SECRET")
        self.endpoint = endpoint.removeprefix("https://").removeprefix("http://").strip("/")
        self.bucket = bucket.strip()
        self.storage_region = region.strip()
        self._access_key_id = access_key_id.strip()
        self._access_key_secret = access_key_secret.strip()
        self._now_epoch = now_epoch

    @classmethod
    def from_env(cls) -> "AliyunOssObjectDeleteService":
        missing = [
            name
            for name in (
                "ALIYUN_OSS_ENDPOINT",
                "ALIYUN_OSS_BUCKET",
                "ALIYUN_OSS_REGION",
                "ALIYUN_OSS_ACCESS_KEY_ID",
                "ALIYUN_OSS_ACCESS_KEY_SECRET",
            )
            if not os.environ.get(name, "").strip()
        ]
        if missing:
            raise ValueError("Missing OSS delete configuration: " + ", ".join(missing))
        return cls(
            endpoint=os.environ["ALIYUN_OSS_ENDPOINT"],
            bucket=os.environ["ALIYUN_OSS_BUCKET"],
            region=os.environ["ALIYUN_OSS_REGION"],
            access_key_id=os.environ["ALIYUN_OSS_ACCESS_KEY_ID"],
            access_key_secret=os.environ["ALIYUN_OSS_ACCESS_KEY_SECRET"],
        )

    def delete_object(self, *, object_key: str) -> dict[str, Any]:
        _validate_object_key(object_key)
        request = Request(self._signed_delete_url(object_key=object_key), method="DELETE")
        try:
            with urlopen(request, timeout=30) as response:
                if response.status not in {200, 202, 204}:
                    raise ValueError("media object could not be deleted")
                return {"deleted": True, "already_missing": False}
        except HTTPError as exc:
            if exc.code == 404:
                return {"deleted": True, "already_missing": True}
            raise ValueError("media object could not be deleted") from exc
        except URLError as exc:
            raise ValueError("media object could not be deleted") from exc

    def _signed_delete_url(self, *, object_key: str, expires_in_seconds: int = 600) -> str:
        _validate_object_key(object_key)
        now = self._now_epoch if self._now_epoch is not None else int(time.time())
        expires = now + expires_in_seconds
        canonical_resource = f"/{self.bucket}/{object_key}"
        string_to_sign = f"DELETE\n\n\n{expires}\n{canonical_resource}"
        signature = base64.b64encode(
            hmac.new(
                self._access_key_secret.encode("utf-8"),
                string_to_sign.encode("utf-8"),
                hashlib.sha1,
            ).digest()
        ).decode("ascii")
        query = urlencode(
            {
                "OSSAccessKeyId": self._access_key_id,
                "Expires": str(expires),
                "Signature": signature,
            }
        )
        return f"https://{self.bucket}.{self.endpoint}/{quote(object_key, safe='/')}?{query}"
