import json
import os
import subprocess

import frappe
import requests

SERVERS_FILE = "/home/frappe/frappe-bench/apps/admin_panel/templates/servers.json"
CERTS_DIR = "/home/frappe/frappe-bench/apps/admin_panel/certs"
CLIENT_CERT = os.path.join(CERTS_DIR, "client.crt")
CLIENT_KEY = os.path.join(CERTS_DIR, "client.key")


def _check_admin_permission():
	"""Check if user has admin permission."""
	if frappe.session.user == "Guest":
		frappe.throw("Please login to access this feature", frappe.AuthenticationError)

	if frappe.session.user == "Administrator":
		return

	roles = frappe.get_roles()
	if "System Manager" not in roles and "Administrator" not in roles:
		frappe.throw(
			"You need System Manager or Administrator role to access this feature",
			frappe.PermissionError,
		)


def _ensure_certs_dir():
	"""Ensure certificates directory exists."""
	if not os.path.exists(CERTS_DIR):
		os.makedirs(CERTS_DIR, mode=0o700, exist_ok=True)


def _load_servers():
	"""Load servers config from JSON file."""
	if os.path.exists(SERVERS_FILE):
		with open(SERVERS_FILE, "r") as f:
			return json.load(f)
	return {}


def _save_servers(servers):
	"""Save servers config to JSON file."""
	os.makedirs(os.path.dirname(SERVERS_FILE), exist_ok=True)
	with open(SERVERS_FILE, "w") as f:
		json.dump(servers, f, indent=2)


def _has_client_certificate():
	"""Check if client certificate exists."""
	return os.path.exists(CLIENT_CERT) and os.path.exists(CLIENT_KEY)


def _check_server_status(url):
	"""Check if server is reachable via REST API using client certificate."""
	if not _has_client_certificate():
		return "no_cert"

	try:
		r = requests.get(
			f"{url}/1.0",
			cert=(CLIENT_CERT, CLIENT_KEY),
			verify=False,
			timeout=10,
		)
		return "online" if r.status_code == 200 else "offline"
	except requests.exceptions.Timeout:
		return "timeout"
	except requests.exceptions.ConnectionError:
		return "offline"
	except Exception:
		return "error"


@frappe.whitelist()
def get_certificate_status():
	"""Get status of client certificate."""
	_check_admin_permission()

	has_cert = _has_client_certificate()

	result = {
		"has_certificate": has_cert,
		"cert_path": CLIENT_CERT,
		"key_path": CLIENT_KEY,
	}

	if has_cert:
		# Get certificate info
		try:
			import subprocess
			res = subprocess.run(
				["openssl", "x509", "-in", CLIENT_CERT, "-noout", "-subject", "-dates"],
				capture_output=True,
				text=True,
				timeout=5,
			)
			if res.returncode == 0:
				result["cert_info"] = res.stdout.strip()
		except Exception:
			pass

	return result


@frappe.whitelist()
def generate_client_certificate():
	"""Generate a new client certificate for the admin panel."""
	_check_admin_permission()
	_ensure_certs_dir()

	try:
		# Generate EC private key
		key_result = subprocess.run(
			["openssl", "ecparam", "-genkey", "-name", "secp384r1", "-out", CLIENT_KEY],
			capture_output=True,
			text=True,
			timeout=30,
		)
		if key_result.returncode != 0:
			return {"success": False, "error": f"Failed to generate key: {key_result.stderr}"}

		os.chmod(CLIENT_KEY, 0o600)

		# Generate self-signed certificate (valid for 10 years)
		cert_result = subprocess.run(
			[
				"openssl", "req", "-new", "-x509",
				"-key", CLIENT_KEY,
				"-out", CLIENT_CERT,
				"-days", "3650",
				"-subj", "/CN=admin-panel/O=Frappe Admin Panel",
			],
			capture_output=True,
			text=True,
			timeout=30,
		)
		if cert_result.returncode != 0:
			return {"success": False, "error": f"Failed to generate certificate: {cert_result.stderr}"}

		os.chmod(CLIENT_CERT, 0o644)

		return {
			"success": True,
			"message": "Certificate generated successfully",
			"cert_path": CLIENT_CERT,
		}

	except subprocess.TimeoutExpired:
		return {"success": False, "error": "Timeout generating certificate"}
	except Exception as e:
		return {"success": False, "error": str(e)}


@frappe.whitelist()
def get_client_certificate():
	"""Get the client certificate content for adding to Incus servers."""
	_check_admin_permission()

	if not _has_client_certificate():
		return {"success": False, "error": "No client certificate. Please generate one first."}

	try:
		with open(CLIENT_CERT, "r") as f:
			cert_content = f.read()

		return {
			"success": True,
			"certificate": cert_content,
			"instructions": "Add this certificate to your Incus server trust store:\n\n"
			               "1. Copy the certificate content and save as 'admin-panel.crt'\n"
			               "2. On the Incus server run: incus config trust add-certificate admin-panel.crt",
		}
	except Exception as e:
		return {"success": False, "error": str(e)}


@frappe.whitelist()
def get_servers():
	"""Get list of all configured servers."""
	_check_admin_permission()

	server_config = _load_servers()
	servers = []

	for name, config in server_config.items():
		url = config.get("url", "")

		# Check server status via REST API
		status = _check_server_status(url) if url else "no_url"

		servers.append({
			"name": name,
			"url": url,
			"is_image_server": config.get("is_image_server", False),
			"is_container_server": config.get("is_container_server", False),
			"status": status,
		})

	return servers


@frappe.whitelist()
def add_server(name, url, is_image_server=False, is_container_server=False):
	"""Add a new Incus server (uses panel's client certificate for auth)."""
	_check_admin_permission()

	# Validate name
	if not name or not all(c.isalnum() or c in "-_" for c in name):
		frappe.throw("Server name must be alphanumeric (dashes and underscores allowed)")

	if name in ("local", "images"):
		frappe.throw("Cannot use reserved names: local, images")

	# Check if already exists
	server_config = _load_servers()
	if name in server_config:
		frappe.throw(f"Server '{name}' already exists")

	# Validate URL format
	if not url or not url.startswith("https://"):
		frappe.throw("Server URL must start with https://")

	# Test connection if certificate exists
	if _has_client_certificate():
		status = _check_server_status(url)
		if status == "offline":
			# Don't block, just warn
			frappe.msgprint(
				f"Warning: Could not connect to server. Make sure to add the panel's certificate to the server's trust store.",
				indicator="orange",
			)

	# Save server config
	server_config[name] = {
		"url": url,
		"is_image_server": bool(is_image_server),
		"is_container_server": bool(is_container_server),
	}
	_save_servers(server_config)

	return {"success": True, "message": f"Server '{name}' added successfully"}


@frappe.whitelist()
def update_server(name, is_image_server=None, is_container_server=None):
	"""Update server roles."""
	_check_admin_permission()

	server_config = _load_servers()
	if name not in server_config:
		frappe.throw(f"Server '{name}' not found")

	if is_image_server is not None:
		server_config[name]["is_image_server"] = bool(is_image_server)

	if is_container_server is not None:
		server_config[name]["is_container_server"] = bool(is_container_server)

	_save_servers(server_config)

	return {"success": True, "message": f"Server '{name}' updated successfully"}


@frappe.whitelist()
def remove_server(name):
	"""Remove a server from configuration."""
	_check_admin_permission()

	server_config = _load_servers()
	if name not in server_config:
		frappe.throw(f"Server '{name}' not found")

	del server_config[name]
	_save_servers(server_config)

	return {"success": True, "message": f"Server '{name}' removed successfully"}


@frappe.whitelist()
def test_connection(name):
	"""Test connection to a server."""
	_check_admin_permission()

	server_config = _load_servers()
	if name not in server_config:
		return {"success": False, "error": f"Server '{name}' not found"}

	url = server_config[name].get("url", "")
	if not url:
		return {"success": False, "error": "Server URL not configured"}

	if not _has_client_certificate():
		return {"success": False, "error": "No client certificate. Please generate one first."}

	status = _check_server_status(url)

	if status == "online":
		return {"success": True, "status": "online", "message": "Connection successful"}
	elif status == "timeout":
		return {"success": False, "status": "timeout", "message": "Connection timeout"}
	elif status == "no_cert":
		return {"success": False, "status": "no_cert", "message": "Certificate not trusted by server"}
	else:
		return {"success": False, "status": "offline", "message": "Connection failed. Check URL and certificate trust."}


@frappe.whitelist()
def get_image_servers():
	"""Get list of servers marked as image servers."""
	_check_admin_permission()

	servers = get_servers()
	return [s for s in servers if s.get("is_image_server")]


@frappe.whitelist()
def get_container_servers():
	"""Get list of servers marked as container servers."""
	_check_admin_permission()

	servers = get_servers()
	return [s for s in servers if s.get("is_container_server")]


@frappe.whitelist()
def get_setup_status():
	"""Check if distrobuilder is installed and certificate exists."""
	_check_admin_permission()

	status = {
		"distrobuilder_installed": False,
		"certificate_exists": _has_client_certificate(),
	}

	# Check distrobuilder
	try:
		result = subprocess.run(
			["which", "distrobuilder"],
			capture_output=True,
			text=True,
			timeout=5,
		)
		status["distrobuilder_installed"] = result.returncode == 0
	except Exception:
		pass

	return status


@frappe.whitelist()
def run_setup(doas_password):
	"""Run setup commands to install distrobuilder."""
	_check_admin_permission()

	if not doas_password:
		return {"success": False, "error": "Password is required"}

	logs = []
	logs.append("=== Starting Distrobuilder Setup ===")

	commands = [
		(["doas", "-S", "apk", "add", "distrobuilder"], "Installing distrobuilder"),
	]

	for cmd, description in commands:
		logs.append(f"\n{description}...")
		try:
			result = subprocess.run(
				cmd,
				input=f"{doas_password}\n",
				capture_output=True,
				text=True,
				timeout=120,
			)

			if result.stdout:
				logs.append(result.stdout.strip())

			if result.returncode != 0:
				error = result.stderr or "Unknown error"
				if "already" in error.lower() or "exists" in error.lower():
					logs.append(f"  (already done)")
				else:
					logs.append(f"Failed: {error}")
					return {"success": False, "error": error, "logs": logs}
			else:
				logs.append(f"{description} - Done")

		except subprocess.TimeoutExpired:
			logs.append(f"Timeout: {description}")
			return {"success": False, "error": f"Timeout during: {description}", "logs": logs}
		except Exception as e:
			logs.append(f"Error: {str(e)}")
			return {"success": False, "error": str(e), "logs": logs}

	logs.append("\n=== Setup completed successfully! ===")

	return {"success": True, "message": "Setup completed successfully", "logs": logs}
