"""
Compute Engine API emulator.

Supports instances, networks, subnetworks, firewalls, and disks —
the core resources Terraform manages via the google_compute_* resources.
"""

from flask import Blueprint, jsonify, request as flask_request

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

SVC = "compute"


class ComputeService(GCPService):
    service_name = "compute"
    api_prefix = COMPUTE_API_PREFIX

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

        # --- Instances ---
        bp.add_url_rule(
            f"{p}/projects/<project>/zones/<zone>/instances",
            "list_instances", self.list_instances, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/zones/<zone>/instances",
            "create_instance", self.create_instance, methods=["POST"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/zones/<zone>/instances/<name>",
            "get_instance", self.get_instance, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/zones/<zone>/instances/<name>",
            "delete_instance", self.delete_instance, methods=["DELETE"],
        )

        # --- Networks ---
        bp.add_url_rule(
            f"{p}/projects/<project>/global/networks",
            "list_networks", self.list_networks, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/global/networks",
            "create_network", self.create_network, methods=["POST"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/global/networks/<name>",
            "get_network", self.get_network, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/global/networks/<name>",
            "delete_network", self.delete_network, methods=["DELETE"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/global/networks/<name>",
            "patch_network", self.patch_network, methods=["PATCH"],
        )

        # --- Subnetworks ---
        bp.add_url_rule(
            f"{p}/projects/<project>/regions/<region>/subnetworks",
            "list_subnetworks", self.list_subnetworks, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/regions/<region>/subnetworks",
            "create_subnetwork", self.create_subnetwork, methods=["POST"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/regions/<region>/subnetworks/<name>",
            "get_subnetwork", self.get_subnetwork, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/regions/<region>/subnetworks/<name>",
            "delete_subnetwork", self.delete_subnetwork, methods=["DELETE"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/regions/<region>/subnetworks/<name>",
            "patch_subnetwork", self.patch_subnetwork, methods=["PATCH"],
        )

        # --- Firewalls ---
        bp.add_url_rule(
            f"{p}/projects/<project>/global/firewalls",
            "list_firewalls", self.list_firewalls, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/global/firewalls",
            "create_firewall", self.create_firewall, methods=["POST"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/global/firewalls/<name>",
            "get_firewall", self.get_firewall, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/global/firewalls/<name>",
            "delete_firewall", self.delete_firewall, methods=["DELETE"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/global/firewalls/<name>",
            "patch_firewall", self.patch_firewall, methods=["PATCH"],
        )

        # --- Disks ---
        bp.add_url_rule(
            f"{p}/projects/<project>/zones/<zone>/disks",
            "list_disks", self.list_disks, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/zones/<zone>/disks",
            "create_disk", self.create_disk, methods=["POST"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/zones/<zone>/disks/<name>",
            "get_disk", self.get_disk, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/zones/<zone>/disks/<name>",
            "delete_disk", self.delete_disk, methods=["DELETE"],
        )

        # --- Operations ---
        bp.add_url_rule(
            f"{p}/projects/<project>/zones/<zone>/operations/<operation>",
            "get_zone_operation", self.get_zone_operation, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/regions/<region>/operations/<operation>",
            "get_region_operation", self.get_region_operation, methods=["GET"],
        )
        bp.add_url_rule(
            f"{p}/projects/<project>/global/operations/<operation>",
            "get_global_operation", self.get_global_operation, methods=["GET"],
        )

        # --- Projects (compute-level: get project to read metadata) ---
        bp.add_url_rule(
            f"{p}/projects/<project>",
            "get_compute_project", self.get_compute_project, methods=["GET"],
        )

    # ── Instances ──────────────────────────────────────────────────────

    def list_instances(self, project: str, zone: str):
        items = self.state.list(SVC, project, f"instances/{zone}")
        return jsonify(paginate(items, flask_request, "compute#instanceList"))

    def create_instance(self, project: str, zone: str):
        body = flask_request.get_json(silent=True) or {}
        name = body.get("name", f"instance-{gcp_id()[:8]}")
        rid = gcp_id()
        sl = self_link(flask_request, f"{self.api_prefix}/projects/{project}/zones/{zone}/instances/{name}")

        instance = {
            "kind": "compute#instance",
            "id": rid,
            "name": name,
            "zone": f"projects/{project}/zones/{zone}",
            "machineType": body.get("machineType", f"projects/{project}/zones/{zone}/machineTypes/e2-medium"),
            "status": "RUNNING",
            "selfLink": sl,
            "networkInterfaces": body.get("networkInterfaces", [
                {
                    "network": f"projects/{project}/global/networks/default",
                    "networkIP": "10.128.0.2",
                    "name": "nic0",
                }
            ]),
            "disks": body.get("disks", []),
            "metadata": body.get("metadata", {"kind": "compute#metadata", "items": []}),
            "tags": body.get("tags", {"items": []}),
            "labels": body.get("labels", {}),
            "scheduling": body.get("scheduling", {
                "onHostMaintenance": "MIGRATE",
                "automaticRestart": True,
                "preemptible": False,
            }),
            "serviceAccounts": body.get("serviceAccounts", []),
            "creationTimestamp": timestamp_now(),
            "canIpForward": body.get("canIpForward", False),
            "deletionProtection": body.get("deletionProtection", False),
            "fingerprint": "mock-fingerprint",
        }
        self.state.put(SVC, project, f"instances/{zone}", name, instance)

        op = self.state.create_operation(SVC, project, zone=zone, target_link=sl)
        return jsonify(op)

    def get_instance(self, project: str, zone: str, name: str):
        inst = self.state.get(SVC, project, f"instances/{zone}", name)
        if not inst:
            return error_response(404, f"Instance {name} not found in zone {zone}")
        return jsonify(inst)

    def delete_instance(self, project: str, zone: str, name: str):
        if not self.state.delete(SVC, project, f"instances/{zone}", name):
            return error_response(404, f"Instance {name} not found")
        op = self.state.create_operation(SVC, project, zone=zone, operation_type="delete")
        return jsonify(op)

    # ── Networks ───────────────────────────────────────────────────────

    def list_networks(self, project: str):
        items = self.state.list(SVC, project, "networks")
        return jsonify(paginate(items, flask_request, "compute#networkList"))

    def create_network(self, project: str):
        body = flask_request.get_json(silent=True) or {}
        name = body.get("name", f"network-{gcp_id()[:8]}")
        sl = self_link(flask_request, f"{self.api_prefix}/projects/{project}/global/networks/{name}")

        network = {
            "kind": "compute#network",
            "id": gcp_id(),
            "name": name,
            "selfLink": sl,
            "autoCreateSubnetworks": body.get("autoCreateSubnetworks", True),
            "routingConfig": body.get("routingConfig", {"routingMode": "REGIONAL"}),
            "mtu": body.get("mtu", 1460),
            "creationTimestamp": timestamp_now(),
            "subnetworks": [],
        }
        self.state.put(SVC, project, "networks", name, network)
        op = self.state.create_operation(SVC, project, target_link=sl)
        return jsonify(op)

    def get_network(self, project: str, name: str):
        net = self.state.get(SVC, project, "networks", name)
        if not net:
            return error_response(404, f"Network {name} not found")
        return jsonify(net)

    def delete_network(self, project: str, name: str):
        if not self.state.delete(SVC, project, "networks", name):
            return error_response(404, f"Network {name} not found")
        op = self.state.create_operation(SVC, project, operation_type="delete")
        return jsonify(op)

    def patch_network(self, project: str, name: str):
        existing = self.state.get(SVC, project, "networks", name)
        if not existing:
            return error_response(404, f"Network {name} not found")
        body = flask_request.get_json(silent=True) or {}
        existing.update({k: v for k, v in body.items() if v is not None})
        self.state.put(SVC, project, "networks", name, existing)
        op = self.state.create_operation(SVC, project, operation_type="patch")
        return jsonify(op)

    # ── Subnetworks ────────────────────────────────────────────────────

    def list_subnetworks(self, project: str, region: str):
        items = self.state.list(SVC, project, f"subnetworks/{region}")
        return jsonify(paginate(items, flask_request, "compute#subnetworkList"))

    def create_subnetwork(self, project: str, region: str):
        body = flask_request.get_json(silent=True) or {}
        name = body.get("name", f"subnet-{gcp_id()[:8]}")
        sl = self_link(flask_request, f"{self.api_prefix}/projects/{project}/regions/{region}/subnetworks/{name}")

        subnet = {
            "kind": "compute#subnetwork",
            "id": gcp_id(),
            "name": name,
            "selfLink": sl,
            "network": body.get("network", ""),
            "ipCidrRange": body.get("ipCidrRange", "10.0.0.0/24"),
            "region": f"projects/{project}/regions/{region}",
            "gatewayAddress": body.get("gatewayAddress", "10.0.0.1"),
            "privateIpGoogleAccess": body.get("privateIpGoogleAccess", False),
            "purpose": body.get("purpose", "PRIVATE"),
            "stackType": body.get("stackType", "IPV4_ONLY"),
            "creationTimestamp": timestamp_now(),
            "fingerprint": "mock-fingerprint",
        }
        self.state.put(SVC, project, f"subnetworks/{region}", name, subnet)
        op = self.state.create_operation(SVC, project, region=region, target_link=sl)
        return jsonify(op)

    def get_subnetwork(self, project: str, region: str, name: str):
        sub = self.state.get(SVC, project, f"subnetworks/{region}", name)
        if not sub:
            return error_response(404, f"Subnetwork {name} not found in region {region}")
        return jsonify(sub)

    def delete_subnetwork(self, project: str, region: str, name: str):
        if not self.state.delete(SVC, project, f"subnetworks/{region}", name):
            return error_response(404, f"Subnetwork {name} not found")
        op = self.state.create_operation(SVC, project, region=region, operation_type="delete")
        return jsonify(op)

    def patch_subnetwork(self, project: str, region: str, name: str):
        existing = self.state.get(SVC, project, f"subnetworks/{region}", name)
        if not existing:
            return error_response(404, f"Subnetwork {name} not found")
        body = flask_request.get_json(silent=True) or {}
        existing.update({k: v for k, v in body.items() if v is not None})
        self.state.put(SVC, project, f"subnetworks/{region}", name, existing)
        op = self.state.create_operation(SVC, project, region=region, operation_type="patch")
        return jsonify(op)

    # ── Firewalls ──────────────────────────────────────────────────────

    def list_firewalls(self, project: str):
        items = self.state.list(SVC, project, "firewalls")
        return jsonify(paginate(items, flask_request, "compute#firewallList"))

    def create_firewall(self, project: str):
        body = flask_request.get_json(silent=True) or {}
        name = body.get("name", f"firewall-{gcp_id()[:8]}")
        sl = self_link(flask_request, f"{self.api_prefix}/projects/{project}/global/firewalls/{name}")

        firewall = {
            "kind": "compute#firewall",
            "id": gcp_id(),
            "name": name,
            "selfLink": sl,
            "network": body.get("network", f"projects/{project}/global/networks/default"),
            "direction": body.get("direction", "INGRESS"),
            "priority": body.get("priority", 1000),
            "allowed": body.get("allowed", []),
            "denied": body.get("denied", []),
            "sourceRanges": body.get("sourceRanges", []),
            "sourceTags": body.get("sourceTags", []),
            "targetTags": body.get("targetTags", []),
            "description": body.get("description", ""),
            "disabled": body.get("disabled", False),
            "creationTimestamp": timestamp_now(),
        }
        self.state.put(SVC, project, "firewalls", name, firewall)
        op = self.state.create_operation(SVC, project, target_link=sl)
        return jsonify(op)

    def get_firewall(self, project: str, name: str):
        fw = self.state.get(SVC, project, "firewalls", name)
        if not fw:
            return error_response(404, f"Firewall {name} not found")
        return jsonify(fw)

    def delete_firewall(self, project: str, name: str):
        if not self.state.delete(SVC, project, "firewalls", name):
            return error_response(404, f"Firewall {name} not found")
        op = self.state.create_operation(SVC, project, operation_type="delete")
        return jsonify(op)

    def patch_firewall(self, project: str, name: str):
        existing = self.state.get(SVC, project, "firewalls", name)
        if not existing:
            return error_response(404, f"Firewall {name} not found")
        body = flask_request.get_json(silent=True) or {}
        existing.update({k: v for k, v in body.items() if v is not None})
        self.state.put(SVC, project, "firewalls", name, existing)
        op = self.state.create_operation(SVC, project, operation_type="patch")
        return jsonify(op)

    # ── Disks ──────────────────────────────────────────────────────────

    def list_disks(self, project: str, zone: str):
        items = self.state.list(SVC, project, f"disks/{zone}")
        return jsonify(paginate(items, flask_request, "compute#diskList"))

    def create_disk(self, project: str, zone: str):
        body = flask_request.get_json(silent=True) or {}
        name = body.get("name", f"disk-{gcp_id()[:8]}")
        sl = self_link(flask_request, f"{self.api_prefix}/projects/{project}/zones/{zone}/disks/{name}")

        disk = {
            "kind": "compute#disk",
            "id": gcp_id(),
            "name": name,
            "selfLink": sl,
            "zone": f"projects/{project}/zones/{zone}",
            "sizeGb": body.get("sizeGb", "10"),
            "type": body.get("type", f"projects/{project}/zones/{zone}/diskTypes/pd-standard"),
            "status": "READY",
            "sourceImage": body.get("sourceImage", ""),
            "labels": body.get("labels", {}),
            "physicalBlockSizeBytes": "4096",
            "creationTimestamp": timestamp_now(),
        }
        self.state.put(SVC, project, f"disks/{zone}", name, disk)
        op = self.state.create_operation(SVC, project, zone=zone, target_link=sl)
        return jsonify(op)

    def get_disk(self, project: str, zone: str, name: str):
        disk = self.state.get(SVC, project, f"disks/{zone}", name)
        if not disk:
            return error_response(404, f"Disk {name} not found in zone {zone}")
        return jsonify(disk)

    def delete_disk(self, project: str, zone: str, name: str):
        if not self.state.delete(SVC, project, f"disks/{zone}", name):
            return error_response(404, f"Disk {name} not found")
        op = self.state.create_operation(SVC, project, zone=zone, operation_type="delete")
        return jsonify(op)

    # ── Operations ─────────────────────────────────────────────────────

    def get_zone_operation(self, project: str, zone: str, operation: str):
        op = self.state.get_operation(SVC, project, operation, zone=zone)
        if not op:
            op = {"kind": "compute#operation", "name": operation, "status": "DONE", "progress": 100}
        return jsonify(op)

    def get_region_operation(self, project: str, region: str, operation: str):
        op = self.state.get_operation(SVC, project, operation, region=region)
        if not op:
            op = {"kind": "compute#operation", "name": operation, "status": "DONE", "progress": 100}
        return jsonify(op)

    def get_global_operation(self, project: str, operation: str):
        op = self.state.get_operation(SVC, project, operation)
        if not op:
            op = {"kind": "compute#operation", "name": operation, "status": "DONE", "progress": 100}
        return jsonify(op)

    # ── Project metadata ───────────────────────────────────────────────

    def get_compute_project(self, project: str):
        return jsonify({
            "kind": "compute#project",
            "id": gcp_id(),
            "name": project,
            "selfLink": self_link(flask_request, f"{self.api_prefix}/projects/{project}"),
            "defaultServiceAccount": f"{project}@developer.gserviceaccount.com",
            "commonInstanceMetadata": {"kind": "compute#metadata", "items": []},
            "quotas": [],
            "creationTimestamp": timestamp_now(),
            "xpnProjectStatus": "UNSPECIFIED_XPN_PROJECT_STATUS",
        })
