# Copyright (c) 2021, JINGROW
# For license information, please see license.txt
from __future__ import annotations

import base64
import hashlib
import ipaddress
import re
import time
import typing
from textwrap import wrap
from typing import ClassVar, Generator

import boto3
import jingrow
from jingrow.model.page import Page
from hcloud import APIException, Client
from hcloud.networks.domain import NetworkSubnet
from oci.config import validate_config
from oci.core import VirtualNetworkClient
from oci.core.models import (
	AddNetworkSecurityGroupSecurityRulesDetails,
	AddSecurityRuleDetails,
	CreateInternetGatewayDetails,
	CreateNetworkSecurityGroupDetails,
	CreateSubnetDetails,
	CreateVcnDetails,
	PortRange,
	RouteRule,
	TcpOptions,
	UpdateRouteTableDetails,
)
from oci.identity import IdentityClient

from jcloud.jcloud.pagetype.virtual_machine_image.virtual_machine_image import (
	VirtualMachineImage,
)
from jcloud.utils import get_current_team, unique

if typing.TYPE_CHECKING:
	from jcloud.jcloud.pagetype.jcloud_settings.jcloud_settings import JcloudSettings
	from jcloud.jcloud.pagetype.server_plan.server_plan import ServerPlan
	from jcloud.jcloud.pagetype.virtual_machine.virtual_machine import VirtualMachine


class Cluster(Page):
	# begin: auto-generated types
	# This code is auto-generated. Do not modify anything in this block.

	from typing import TYPE_CHECKING

	if TYPE_CHECKING:
		from jingrow.types import DF

		availability_zone: DF.Data | None
		aws_access_key_id: DF.Data | None
		aws_secret_access_key: DF.Password | None
		beta: DF.Check
		cidr_block: DF.Data | None
		cloud_provider: DF.Literal["AWS EC2", "Generic", "OCI", "Hetzner"]
		description: DF.Data | None
		hybrid: DF.Check
		image: DF.AttachImage | None
		monitoring_password: DF.Password | None
		network_acl_id: DF.Data | None
		oci_private_key: DF.Password | None
		oci_public_key: DF.Code | None
		oci_tenancy: DF.Data | None
		oci_user: DF.Data | None
		proxy_security_group_id: DF.Data | None
		public: DF.Check
		region: DF.Link | None
		route_table_id: DF.Data | None
		security_group_id: DF.Data | None
		ssh_key: DF.Link | None
		status: DF.Literal["Active", "Copying Images", "Archived"]
		subnet_cidr_block: DF.Data | None
		subnet_id: DF.Data | None
		team: DF.Link | None
		title: DF.Data | None
		vpc_id: DF.Data | None
	# end: auto-generated types

	dashboard_fields: ClassVar[list[str]] = ["title", "image"]

	base_servers: ClassVar[dict[str, str]] = {
		"Proxy Server": "n",
		"Database Server": "m",
		"Server": "f",  # App server is last as it needs both proxy and db server
	}

	private_servers: ClassVar[dict] = {
		# TODO: Uncomment these when they are implemented
		# "Monitor Server": "p",
		# "Log Server": "e,
	}

	wait_for_aws_creds_seconds = 20

	@staticmethod
	def get_list_query(query, filters=None, **list_args):
		if filters and filters.get("group"):
			rg = jingrow.get_pg("Release Group", filters.get("group"))
			cluster_names = rg.get_clusters()
			return jingrow.get_all(
				"Cluster",
				fields=["name", "title", "image", "beta"],
				filters={"name": ("in", cluster_names)},
			)
		return None

	def validate(self):
		self.validate_monitoring_password()
		self.validate_cidr_block()
		if self.cloud_provider == "AWS EC2":
			self.validate_aws_credentials()
		elif self.cloud_provider == "OCI":
			self.set_oci_availability_zone()
		elif self.cloud_provider == "Hetzner":
			self.validate_hetzner_api_token()

	def validate_hetzner_api_token(self):
		settings: "JcloudSettings" = jingrow.get_single("Jcloud Settings")
		api_token = settings.get_password("hetzner_api_token")
		client = Client(token=api_token)
		try:
			# Check if we can list servers (read access)
			servers = client.servers.get_all()

			if servers is None:
				jingrow.throw("API token does not have read access to the Hetzner Cloud.")

		except APIException as e:
			# Handle specific API exceptions like unauthorized access
			if e.code == "unauthorized":
				jingrow.throw("API token is invalid or does not have the correct permissions.")
			else:
				jingrow.throw(f"An error occurred while validating the API token: {e}")

	def validate_aws_credentials(self):
		settings: "JcloudSettings" = jingrow.get_single("Jcloud Settings")
		if self.public and not self.aws_access_key_id:
			self.aws_access_key_id = settings.aws_access_key_id
			self.aws_secret_access_key = settings.get_password("aws_secret_access_key")
		elif not self.aws_access_key_id or not self.aws_secret_access_key:
			root_client = settings.boto3_iam_client
			group = (  # make sure group exists
				root_client.get_group(GroupName="fc-vpc-customer").get("Group", {}).get("GroupName")
			)
			root_client.create_user(
				UserName=jingrow.scrub(self.name),
			)
			root_client.add_user_to_group(GroupName=group, UserName=jingrow.scrub(self.name))
			access_key_pair = root_client.create_access_key(
				UserName=jingrow.scrub(self.name),
			)["AccessKey"]

			self.aws_access_key_id = access_key_pair["AccessKeyId"]
			self.aws_secret_access_key = access_key_pair["SecretAccessKey"]
			from time import sleep

			sleep(self.wait_for_aws_creds_seconds)  # wait for key to be valid

	def after_insert(self):
		if self.cloud_provider == "AWS EC2":
			self.provision_on_aws_ec2()
		elif self.cloud_provider == "OCI":
			self.provision_on_oci()
		elif self.cloud_provider == "Hetzner":
			self.provision_on_hetzner()

	def provision_on_hetzner(self):
		try:
			# Define the subnet
			subnets = [
				NetworkSubnet(
					type="cloud",  # VPCs in Hetzner are defined as 'cloud' subnets
					ip_range=self.subnet_cidr_block,
					network_zone=self.availability_zone,
				)
			]

			# Get Hetzner API token from Jcloud Settings
			settings: "JcloudSettings" = jingrow.get_single("Jcloud Settings")
			api_token = settings.get_password("hetzner_api_token")

			client = Client(token=api_token)

			# Create the network (VPC) on Hetzner
			network = client.networks.create(
				name=f"Jingrow - {self.name}",
				ip_range=self.cidr_block,  # The IP range for the entire network (CIDR)
				subnets=subnets,
				routes=[],
			)
			self.vpc_id = network.id
			self.save()

		except APIException as e:
			jingrow.throw(f"Failed to provision network on Hetzner: {e!s}")

		except Exception as e:
			jingrow.throw(f"An unexpected error occurred during provisioning: {e!s}")

	def on_trash(self):
		machines = jingrow.get_all(
			"Virtual Machine",
			{"cluster": self.name, "status": "Terminated"},
			pluck="name",
		)
		for machine in machines:
			jingrow.delete_pg("Virtual Machine", machine)

	@jingrow.whitelist()
	def add_images(self):
		if self.images_available == 1:
			jingrow.throw("Images are already available", jingrow.ValidationError)
		if not set(self.get_other_region_vmis(get_series=True)) - set(
			self.get_same_region_vmis(get_series=True)
		):
			jingrow.throw(
				"Images for required series not available in other regions. Create Images from server docs.",
				jingrow.ValidationError,
			)
		jingrow.enqueue_pg(self.pagetype, self.name, "_add_images", queue="long", timeout=1200)

	def _add_images(self):
		"""Copies VMIs required for the cluster"""
		self.db_set("status", "Copying Images")
		jingrow.db.commit()
		for vmi in self.copy_virtual_machine_images():
			vmi.wait_for_availability()
		self.reload()
		self.db_set("status", "Active")

	@property
	def images_available(self) -> float:
		return len(self.get_same_region_vmis()) / len(self.server_pagetypes)

	def validate_cidr_block(self):
		if not self.cidr_block:
			blocks = ipaddress.ip_network("10.0.0.0/8").subnets(new_prefix=16)
			existing_blocks = ["10.0.0.0/16"] + jingrow.get_all("Cluster", ["cidr_block"], pluck="cidr_block")  # noqa: RUF005
			for block in blocks:
				cidr_block = str(block)
				if cidr_block not in existing_blocks:
					self.cidr_block = cidr_block
					self.subnet_cidr_block = cidr_block
					break
		if not self.cidr_block:
			jingrow.throw("No CIDR block available", jingrow.ValidationError)

	def validate_monitoring_password(self):
		if not self.monitoring_password:
			self.monitoring_password = jingrow.generate_hash()

	def provision_on_aws_ec2(self):
		client = boto3.client(
			"ec2",
			region_name=self.region,
			aws_access_key_id=self.aws_access_key_id,
			aws_secret_access_key=self.get_password("aws_secret_access_key"),
		)

		response = client.create_vpc(
			AmazonProvidedIpv6CidrBlock=False,
			InstanceTenancy="default",
			TagSpecifications=[
				{
					"ResourceType": "vpc",
					"Tags": [{"Key": "Name", "Value": f"Jingrow - {self.name}"}],
				},
			],
			CidrBlock=self.cidr_block,
		)
		self.vpc_id = response["Vpc"]["VpcId"]

		client.modify_vpc_attribute(VpcId=self.vpc_id, EnableDnsHostnames={"Value": True})

		response = client.create_subnet(
			TagSpecifications=[
				{
					"ResourceType": "subnet",
					"Tags": [
						{
							"Key": "Name",
							"Value": f"Jingrow - {self.name} - Public Subnet",
						}
					],
				},
			],
			AvailabilityZone=self.availability_zone,
			VpcId=self.vpc_id,
			CidrBlock=self.subnet_cidr_block,
		)
		self.subnet_id = response["Subnet"]["SubnetId"]

		response = client.create_internet_gateway(
			TagSpecifications=[
				{
					"ResourceType": "internet-gateway",
					"Tags": [
						{
							"Key": "Name",
							"Value": f"Jingrow - {self.name} - Internet Gateway",
						},
					],
				},
			],
		)

		self.internet_gateway_id = response["InternetGateway"]["InternetGatewayId"]

		client.attach_internet_gateway(InternetGatewayId=self.internet_gateway_id, VpcId=self.vpc_id)

		response = client.describe_route_tables(
			Filters=[{"Name": "vpc-id", "Values": [self.vpc_id]}],
		)
		self.route_table_id = response["RouteTables"][0]["RouteTableId"]

		client.create_route(
			DestinationCidrBlock="0.0.0.0/0",
			GatewayId=self.internet_gateway_id,
			RouteTableId=self.route_table_id,
		)

		client.create_tags(
			Resources=[self.route_table_id],
			Tags=[{"Key": "Name", "Value": f"Jingrow - {self.name} - Route Table"}],
		)

		response = client.describe_network_acls(
			Filters=[{"Name": "vpc-id", "Values": [self.vpc_id]}],
		)
		self.network_acl_id = response["NetworkAcls"][0]["NetworkAclId"]
		client.create_tags(
			Resources=[self.network_acl_id],
			Tags=[{"Key": "Name", "Value": f"Jingrow - {self.name} - Network ACL"}],
		)

		response = client.create_security_group(
			GroupName=f"Jingrow - {self.name} - Security Group",
			Description="Allow Everything",
			VpcId=self.vpc_id,
			TagSpecifications=[
				{
					"ResourceType": "security-group",
					"Tags": [
						{
							"Key": "Name",
							"Value": f"Jingrow - {self.name} - Security Group",
						},
					],
				},
			],
		)
		self.security_group_id = response["GroupId"]

		client.authorize_security_group_ingress(
			GroupId=self.security_group_id,
			IpPermissions=[
				{
					"FromPort": 80,
					"IpProtocol": "tcp",
					"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTP from anywhere"}],
					"ToPort": 80,
				},
				{
					"FromPort": 443,
					"IpProtocol": "tcp",
					"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTPS from anywhere"}],
					"ToPort": 443,
				},
				{
					"FromPort": 22,
					"IpProtocol": "tcp",
					"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "SSH from anywhere"}],
					"ToPort": 22,
				},
				{
					"FromPort": 3306,
					"IpProtocol": "tcp",
					"IpRanges": [
						{
							"CidrIp": self.subnet_cidr_block,
							"Description": "MariaDB from private network",
						}
					],
					"ToPort": 3306,
				},
				{
					"FromPort": 22000,
					"IpProtocol": "tcp",
					"IpRanges": [
						{
							"CidrIp": self.subnet_cidr_block,
							"Description": "SSH from private network",
						}
					],
					"ToPort": 22999,
				},
				{
					"FromPort": -1,
					"IpProtocol": "icmp",
					"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "ICMP from anywhere"}],
					"ToPort": -1,
				},
			],
		)
		self.create_proxy_security_group()

		try:  # noqa: SIM105
			# We don't care if the key already exists in this region
			response = client.import_key_pair(
				KeyName=self.ssh_key,
				PublicKeyMaterial=jingrow.db.get_value("SSH Key", self.ssh_key, "public_key"),
				TagSpecifications=[
					{
						"ResourceType": "key-pair",
						"Tags": [{"Key": "Name", "Value": self.ssh_key}],
					},
				],
			)
		except Exception:
			pass
		self.save()

	def create_proxy_security_group(self):
		client = boto3.client(
			"ec2",
			region_name=self.region,
			aws_access_key_id=self.aws_access_key_id,
			aws_secret_access_key=self.get_password("aws_secret_access_key"),
		)
		response = client.create_security_group(
			GroupName=f"Jingrow - {self.name} - Proxy - Security Group",
			Description="Allow Everything on Proxy",
			VpcId=self.vpc_id,
			TagSpecifications=[
				{
					"ResourceType": "security-group",
					"Tags": [
						{
							"Key": "Name",
							"Value": f"Jingrow - {self.name} - Proxy - Security Group",
						},
					],
				},
			],
		)
		self.proxy_security_group_id = response["GroupId"]

		client.authorize_security_group_ingress(
			GroupId=self.proxy_security_group_id,
			IpPermissions=[
				{
					"FromPort": 2222,
					"IpProtocol": "tcp",
					"IpRanges": [
						{
							"CidrIp": "0.0.0.0/0",
							"Description": "SSH proxy from anywhere",
						}
					],
					"ToPort": 2222,
				},
				{
					"FromPort": 3306,
					"IpProtocol": "tcp",
					"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "MariaDB from anywhere"}],
					"ToPort": 3306,
				},
			],
		)

	def get_oci_public_key_fingerprint(self):
		match = re.match(
			r"-*BEGIN PUBLIC KEY-*(.*?)-*END PUBLIC KEY-*",
			"".join(self.oci_public_key.splitlines()),
		)
		base64_key = match.group(1).encode("utf-8")
		binary_key = base64.b64decode(base64_key)
		digest = hashlib.md5(binary_key).hexdigest()
		return ":".join(wrap(digest, 2))

	def get_oci_config(self):
		# Stupid Password field, replaces newines with spaces
		private_key = (
			self.get_password("oci_private_key").replace(" ", "\n").replace("\nPRIVATE\n", " PRIVATE ")
		)

		config = {
			"user": self.oci_user,
			"fingerprint": self.get_oci_public_key_fingerprint(),
			"tenancy": self.oci_tenancy,
			"region": self.region,
			"key_content": private_key,
		}
		validate_config(config)
		return config

	def set_oci_availability_zone(self):
		identity_client = IdentityClient(self.get_oci_config())
		availability_domain = identity_client.list_availability_domains(self.oci_tenancy).data[0].name
		self.availability_zone = availability_domain

	def provision_on_oci(self):
		vcn_client = VirtualNetworkClient(self.get_oci_config())
		vcn = vcn_client.create_vcn(
			CreateVcnDetails(
				compartment_id=self.oci_tenancy,
				display_name=f"Jingrow - {self.name}",
				cidr_block=self.subnet_cidr_block,
			)
		).data
		self.vpc_id = vcn.id
		self.route_table_id = vcn.default_route_table_id
		self.network_acl_id = vcn.default_security_list_id

		time.sleep(1)
		# https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
		# 1 ICMP
		# 6 TCP
		# 17 UDP
		security_group = vcn_client.create_network_security_group(
			CreateNetworkSecurityGroupDetails(
				compartment_id=self.oci_tenancy,
				display_name=f"Jingrow - {self.name} - Security Group",
				vcn_id=self.vpc_id,
			)
		).data
		self.security_group_id = security_group.id

		time.sleep(1)
		vcn_client.add_network_security_group_security_rules(
			self.security_group_id,
			AddNetworkSecurityGroupSecurityRulesDetails(
				security_rules=[
					AddSecurityRuleDetails(
						description="HTTP from anywhere",
						direction="INGRESS",
						protocol="6",
						source="0.0.0.0/0",
						tcp_options=TcpOptions(destination_port_range=PortRange(min=80, max=80)),
					),
					AddSecurityRuleDetails(
						description="HTTPS from anywhere",
						direction="INGRESS",
						protocol="6",
						source="0.0.0.0/0",
						tcp_options=TcpOptions(destination_port_range=PortRange(min=443, max=443)),
					),
					AddSecurityRuleDetails(
						description="SSH from anywhere",
						direction="INGRESS",
						protocol="6",
						source="0.0.0.0/0",
						tcp_options=TcpOptions(destination_port_range=PortRange(min=22, max=22)),
					),
					AddSecurityRuleDetails(
						description="MariaDB from private network",
						direction="INGRESS",
						protocol="6",
						source=self.subnet_cidr_block,
						tcp_options=TcpOptions(destination_port_range=PortRange(min=3306, max=3306)),
					),
					AddSecurityRuleDetails(
						description="SSH from private network",
						direction="INGRESS",
						protocol="6",
						source=self.subnet_cidr_block,
						tcp_options=TcpOptions(destination_port_range=PortRange(min=22000, max=22999)),
					),
					AddSecurityRuleDetails(
						description="ICMP from anywhere",
						direction="INGRESS",
						protocol="1",
						source="0.0.0.0/0",
						# Ignoring IcmpOptions for now. https://docs.oracle.com/en-us/iaas/tools/python/2.117.0/api/core/models/oci.core.models.IcmpOptions.html#oci.core.models.IcmpOptions
					),
					AddSecurityRuleDetails(
						description="Everything to anywhere",
						direction="EGRESS",
						protocol="all",
						destination="0.0.0.0/0",
					),
				],
			),
		)

		time.sleep(1)
		proxy_security_group = vcn_client.create_network_security_group(
			CreateNetworkSecurityGroupDetails(
				compartment_id=self.oci_tenancy,
				display_name=f"Jingrow - {self.name} - Proxy - Security Group",
				vcn_id=self.vpc_id,
			)
		).data
		self.proxy_security_group_id = proxy_security_group.id

		time.sleep(1)
		vcn_client.add_network_security_group_security_rules(  # noqa: B018
			self.proxy_security_group_id,
			AddNetworkSecurityGroupSecurityRulesDetails(
				security_rules=[
					AddSecurityRuleDetails(
						description="SSH proxy from anywhere",
						direction="INGRESS",
						protocol="6",
						source="0.0.0.0/0",
						tcp_options=TcpOptions(destination_port_range=PortRange(min=2222, max=2222)),
					),
					AddSecurityRuleDetails(
						description="MariaDB from anywhere",
						direction="INGRESS",
						protocol="6",
						source="0.0.0.0/0",
						tcp_options=TcpOptions(destination_port_range=PortRange(min=3306, max=3306)),
					),
					AddSecurityRuleDetails(
						description="Everything to anywhere",
						direction="EGRESS",
						protocol="all",
						destination="0.0.0.0/0",
					),
				],
			),
		).data

		time.sleep(1)
		subnet = vcn_client.create_subnet(
			CreateSubnetDetails(
				compartment_id=self.oci_tenancy,
				display_name=f"Jingrow - {self.name} - Public Subnet",
				vcn_id=self.vpc_id,
				cidr_block=self.subnet_cidr_block,
				route_table_id=self.route_table_id,
				security_list_ids=[self.network_acl_id],
			)
		).data
		self.subnet_id = subnet.id

		time.sleep(1)
		# Don't associate IGW with any route table Otherwise it fails with "Rules in the route table must use private IP"
		internet_gateway = vcn_client.create_internet_gateway(
			CreateInternetGatewayDetails(
				compartment_id=self.oci_tenancy,
				display_name=f"Jingrow - {self.name} - Internet Gateway",
				is_enabled=True,
				vcn_id=self.vpc_id,
			)
		).data
		self.internet_gateway_id = internet_gateway.id

		time.sleep(1)
		vcn_client.update_route_table(
			self.route_table_id,
			UpdateRouteTableDetails(
				route_rules=[
					RouteRule(
						destination="0.0.0.0/0",
						network_entity_id=self.internet_gateway_id,
					)
				]
			),
		)

		self.save()

	def get_available_vmi(self, series, platform=None) -> str | None:
		"""Virtual Machine Image available in region for given series"""
		return VirtualMachineImage.get_available_for_series(series, self.region, platform=platform)

	@property
	def server_pagetypes(self):
		server_pagetypes = {**self.base_servers}
		if not self.public:
			server_pagetypes = {**server_pagetypes, **self.private_servers}
		return server_pagetypes

	def get_same_region_vmis(self, get_series=False):
		return jingrow.get_all(
			"Virtual Machine Image",
			filters={
				"region": self.region,
				"series": ("in", list(self.server_pagetypes.values())),
				"status": "Available",
			},
			pluck="name" if not get_series else "series",
		)

	def get_other_region_vmis(self, get_series=False):
		vmis = []
		for series in list(self.server_pagetypes.values()):
			vmis.extend(
				jingrow.get_all(
					"Virtual Machine Image",
					["name", "series", "creation"],
					filters={
						"region": ("!=", self.region),
						"series": series,
						"status": "Available",
					},
					limit=1,
					order_by="creation DESC",
					pluck="name" if not get_series else "series",
				)
			)

		return vmis

	def copy_virtual_machine_images(self) -> Generator[VirtualMachineImage, None, None]:
		"""Creates VMIs required for the cluster"""
		copies = []
		for vmi in self.get_other_region_vmis():
			copies.append(
				jingrow.get_pg(
					"Virtual Machine Image",
					vmi,
				).copy_image(self.name)
			)
			jingrow.db.commit()
		yield from copies

	@jingrow.whitelist()
	def create_servers(self):
		"""Creates servers for the cluster"""
		if self.images_available < 1:
			jingrow.throw(
				"Images are not available. Add them or wait for copy to complete",
				jingrow.ValidationError,
			)
		if self.status != "Active":
			jingrow.throw("Cluster is not active", jingrow.ValidationError)

		for pagetype, _ in self.base_servers.items():
			# TODO: remove Test title #
			server, _ = self.create_server(
				pagetype,
				"Test",
			)
			match pagetype:  # for populating Server pg's fields; assume the trio is created together
				case "Database Server":
					self.database_server = server.name
				case "Proxy Server":
					self.proxy_server = server.name
		if self.public:
			return
		for pagetype, _ in self.private_servers.items():
			self.create_server(
				pagetype,
				"Test",
				create_subscription=False,
			)

	def create_vm(
		self, machine_type: str, platform: str, disk_size: int, domain: str, series: str, team: str
	) -> "VirtualMachine":
		return jingrow.get_pg(
			{
				"pagetype": "Virtual Machine",
				"cluster": self.name,
				"domain": domain,
				"series": series,
				"disk_size": disk_size,
				"machine_type": machine_type,
				"virtual_machine_image": self.get_available_vmi(series, platform=platform),
				"team": team,
			},
		).insert()

	def get_or_create_basic_plan(self, server_type):
		plan = jingrow.db.exists("Server Plan", f"Basic Cluster - {server_type}")
		if plan:
			return jingrow.get_pg("Server Plan", f"Basic Cluster - {server_type}")
		return jingrow.get_pg(
			{
				"pagetype": "Server Plan",
				"name": f"Basic Cluster - {server_type}",
				"title": f"Basic Cluster - {server_type}",
				"instance_type": "t2.medium",
				"price_cny": 0,
				"price_usd": 0,
				"vcpu": 2,
				"memory": 4096,
				"disk": 50,
			}
		).insert(ignore_permissions=True, ignore_if_duplicate=True)

	def create_server(
		self,
		pagetype: str,
		title: str,
		plan: "ServerPlan" = None,
		domain: str | None = None,
		team: str | None = None,
		create_subscription=True,
	):
		"""Creates a server for the cluster"""
		domain = domain or jingrow.db.get_single_value("Jcloud Settings", "domain")
		server_series = {**self.base_servers, **self.private_servers}
		team = team or get_current_team()
		plan = plan or self.get_or_create_basic_plan(pagetype)
		vm = self.create_vm(
			plan.instance_type, plan.platform, plan.disk, domain, server_series[pagetype], team
		)
		server = None
		match pagetype:
			case "Database Server":
				server = vm.create_database_server()
				server.ram = plan.memory
				server.title = f"{title} - Database"
			case "Server":
				server = vm.create_server()
				server.title = f"{title} - Application"
				server.ram = plan.memory
				server.database_server = self.database_server
				server.proxy_server = self.proxy_server
				server.new_worker_allocation = True
			case "Proxy Server":
				server = vm.create_proxy_server()
				server.title = f"{title} - Proxy"
			case "Monitor Server":
				server = vm.create_monitor_server()
				server.title = f"{title} - Monitor"
			case "Log Server":
				server = vm.create_log_server()
				server.title = f"{title} - Log"

		if create_subscription:
			server.plan = plan.name
			server.save()
			server.create_subscription(plan.name)
		job = server.run_jcloud_job("Create Server")

		return server, job

	@classmethod
	def get_all_for_new_bench(cls, extra_filters=None) -> list[dict[str, str]]:
		if extra_filters is None:
			extra_filters = {}
		cluster_names = unique(jingrow.db.get_all("Server", filters={"status": "Active"}, pluck="cluster"))
		filters = {"name": ("in", cluster_names), "public": True}
		return jingrow.db.get_all(
			"Cluster",
			filters={**filters, **extra_filters},
			fields=["name", "title", "image", "beta"],
		)
