"""
Cloud Storage API emulator.

Handles buckets and objects for Terraform's google_storage_bucket
and google_storage_bucket_object resources.
"""

import base64
import hashlib
import time

from flask import Blueprint, jsonify, request as flask_request, Response

from gcplocal.services.base import GCPService
from gcplocal.constants import STORAGE_API_PREFIX
from gcplocal.utils import error_response, gcp_id, timestamp_now, paginate

SVC = "storage"


class StorageService(GCPService):
    service_name = "storage"
    api_prefix = STORAGE_API_PREFIX

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._object_data: dict = {}

    def register_routes(self, bp: Blueprint) -> None:
        p = self.api_prefix

        # --- Buckets ---
        bp.add_url_rule(f"{p}/b", "list_buckets", self.list_buckets, methods=["GET"])
        bp.add_url_rule(f"{p}/b", "create_bucket", self.create_bucket, methods=["POST"])
        bp.add_url_rule(f"{p}/b/<bucket>", "get_bucket", self.get_bucket, methods=["GET"])
        bp.add_url_rule(f"{p}/b/<bucket>", "delete_bucket", self.delete_bucket, methods=["DELETE"])
        bp.add_url_rule(f"{p}/b/<bucket>", "patch_bucket", self.patch_bucket, methods=["PATCH", "PUT"])

        # --- Bucket IAM ---
        bp.add_url_rule(
            f"{p}/b/<bucket>/iam",
            "get_bucket_iam", self.get_bucket_iam, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/b/<bucket>/iam",
            "set_bucket_iam", self.set_bucket_iam, methods=["PUT"],
        )

        # --- Objects ---
        bp.add_url_rule(f"{p}/b/<bucket>/o", "list_objects", self.list_objects, methods=["GET"])
        bp.add_url_rule(
            "/upload/storage/v1/b/<bucket>/o",
            "upload_object", self.upload_object, methods=["POST"],
        )
        bp.add_url_rule(f"{p}/b/<bucket>/o/<path:obj>", "get_object", self.get_object, methods=["GET"])
        bp.add_url_rule(
            f"{p}/b/<bucket>/o/<path:obj>",
            "delete_object", self.delete_object, methods=["DELETE"],
        )

    # ── Buckets ────────────────────────────────────────────────────────

    def list_buckets(self):
        project = flask_request.args.get("project", "default")
        items = self.state.list(SVC, project, "buckets")
        return jsonify(paginate(items, flask_request, "storage#buckets"))

    def create_bucket(self):
        project = flask_request.args.get("project", "default")
        body = flask_request.get_json(silent=True) or {}
        name = body.get("name", f"bucket-{gcp_id()[:8]}")

        bucket = {
            "kind": "storage#bucket",
            "id": name,
            "name": name,
            "selfLink": f"https://www.googleapis.com/storage/v1/b/{name}",
            "projectNumber": gcp_id(),
            "location": body.get("location", "US"),
            "storageClass": body.get("storageClass", "STANDARD"),
            "etag": "CAE=",
            "timeCreated": timestamp_now(),
            "updated": timestamp_now(),
            "metageneration": "1",
            "iamConfiguration": {
                "bucketPolicyOnly": {"enabled": False},
                "uniformBucketLevelAccess": body.get("iamConfiguration", {}).get(
                    "uniformBucketLevelAccess", {"enabled": False}
                ),
                "publicAccessPrevention": "inherited",
            },
            "locationType": "multi-region" if len(body.get("location", "US")) <= 2 else "region",
            "versioning": body.get("versioning", {"enabled": False}),
            "labels": body.get("labels", {}),
            "lifecycle": body.get("lifecycle", {"rule": []}),
            "cors": body.get("cors", []),
            "logging": body.get("logging", {}),
            "website": body.get("website", {}),
            "encryption": body.get("encryption", {}),
            "retentionPolicy": body.get("retentionPolicy", {}),
        }

        self.state.put(SVC, project, "buckets", name, bucket)
        return jsonify(bucket)

    def get_bucket(self, bucket: str):
        for project in self._find_projects_with_bucket(bucket):
            b = self.state.get(SVC, project, "buckets", bucket)
            if b:
                return jsonify(b)
        return error_response(404, f"Bucket {bucket} not found", "NOT_FOUND")

    def delete_bucket(self, bucket: str):
        for project in self._find_projects_with_bucket(bucket):
            if self.state.delete(SVC, project, "buckets", bucket):
                return "", 204
        return error_response(404, f"Bucket {bucket} not found")

    def patch_bucket(self, bucket: str):
        for project in self._find_projects_with_bucket(bucket):
            existing = self.state.get(SVC, project, "buckets", bucket)
            if existing:
                body = flask_request.get_json(silent=True) or {}
                existing.update({k: v for k, v in body.items() if v is not None})
                existing["updated"] = timestamp_now()
                existing["metageneration"] = str(int(existing.get("metageneration", "1")) + 1)
                self.state.put(SVC, project, "buckets", bucket, existing)
                return jsonify(existing)
        return error_response(404, f"Bucket {bucket} not found")

    # ── Bucket IAM ─────────────────────────────────────────────────────

    def get_bucket_iam(self, bucket: str):
        policy = self.state.get(SVC, "global", "bucket_iam", bucket)
        if not policy:
            policy = {
                "kind": "storage#policy",
                "resourceId": f"projects/_/buckets/{bucket}",
                "version": 1,
                "etag": "CAE=",
                "bindings": [],
            }
        return jsonify(policy)

    def set_bucket_iam(self, bucket: str):
        body = flask_request.get_json(silent=True) or {}
        policy = {
            "kind": "storage#policy",
            "resourceId": f"projects/_/buckets/{bucket}",
            "version": body.get("version", 1),
            "etag": "CAI=",
            "bindings": body.get("bindings", []),
        }
        self.state.put(SVC, "global", "bucket_iam", bucket, policy)
        return jsonify(policy)

    # ── Objects ────────────────────────────────────────────────────────

    def list_objects(self, bucket: str):
        items = self.state.list(SVC, "global", f"objects/{bucket}")
        prefix = flask_request.args.get("prefix", "")
        if prefix:
            items = [i for i in items if i.get("name", "").startswith(prefix)]
        return jsonify(paginate(items, flask_request, "storage#objects"))

    def upload_object(self, bucket: str):
        name = flask_request.args.get("name", "unnamed")
        data = flask_request.get_data()
        md5 = base64.b64encode(hashlib.md5(data).digest()).decode()
        crc32c = "AAAAAA=="

        obj = {
            "kind": "storage#object",
            "id": f"{bucket}/{name}",
            "name": name,
            "bucket": bucket,
            "size": str(len(data)),
            "contentType": flask_request.content_type or "application/octet-stream",
            "md5Hash": md5,
            "crc32c": crc32c,
            "etag": "CAAE",
            "generation": str(int(time.time() * 1000000)),
            "metageneration": "1",
            "timeCreated": timestamp_now(),
            "updated": timestamp_now(),
            "storageClass": "STANDARD",
        }

        self.state.put(SVC, "global", f"objects/{bucket}", name, obj)
        self._object_data[f"{bucket}/{name}"] = data
        return jsonify(obj)

    def get_object(self, bucket: str, obj: str):
        alt = flask_request.args.get("alt", "json")
        if alt == "media":
            data = self._object_data.get(f"{bucket}/{obj}", b"")
            return Response(data, content_type="application/octet-stream")

        o = self.state.get(SVC, "global", f"objects/{bucket}", obj)
        if not o:
            return error_response(404, f"Object {obj} not found in bucket {bucket}")
        return jsonify(o)

    def delete_object(self, bucket: str, obj: str):
        self.state.delete(SVC, "global", f"objects/{bucket}", obj)
        self._object_data.pop(f"{bucket}/{obj}", None)
        return "", 204

    # ── Helpers ────────────────────────────────────────────────────────

    def _find_projects_with_bucket(self, bucket_name: str) -> list:
        """Search all projects for a bucket (buckets are globally unique)."""
        projects = set()
        for key in list(self.state._resources.keys()):
            if key.startswith("storage/") and key.endswith(f"/buckets/{bucket_name}"):
                parts = key.split("/")
                if len(parts) >= 4:
                    projects.add(parts[1])
        return list(projects) if projects else ["default"]
