# -*- coding: utf-8 -*-
# Copyright (c) 2020, JINGROW
# For license information, please see license.txt


import os
import secrets
import shlex
import shutil
import subprocess
from datetime import datetime
from pathlib import Path

import jingrow
import OpenSSL
from jingrow.model.page import Page

from jcloud.utils import developer_mode_only


class CertificateAuthority(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

		common_name: DF.Data
		decoded_certificate: DF.Code | None
		directory: DF.Data
		expires_on: DF.Datetime | None
		is_root_ca: DF.Check
		issued_on: DF.Datetime | None
		organization: DF.Data
		organizational_unit: DF.Data
		parent_authority: DF.Link | None
		rsa_key_size: DF.Literal["2048", "3072", "4096"]
		validity_days: DF.Int
	# end: auto-generated types

	def onload(self):
		developer_mode_only()

	def validate(self):
		developer_mode_only()
		self.setup_directory()
		self.generate_private_key()
		if self.is_root_ca:
			self.generate_root_certificate()
		else:
			self.generate_certificate_signing_request()
			self.sign_certificate_signing_request()
			self.generate_chain_certificate()
		self.extract_certificate_details()

	def setup_directory(self):
		if os.path.exists(self.directory):
			shutil.rmtree(self.directory)
		os.mkdir(self.directory)

		os.mkdir(self.new_certificates_directory)
		Path(self.database_file).touch()
		with open(self.serial_file, "w") as f:
			f.write(f"{secrets.randbits(16*8):0{32}x}\n")

		template = "root.conf" if self.is_root_ca else "intermediate.conf"
		template = f"jcloud/jcloud/pagetype/certificate_authority/{template}"
		with open(self.openssl_config_file, "w") as f:
			openssl_config = jingrow.render_template(template, {"pg": self}, is_path=True)
			f.write(openssl_config)

	def run(self, command):
		return subprocess.check_output(shlex.split(command)).decode()

	def generate_private_key(self):
		self.run(f"openssl genrsa -out {self.private_key_file} {self.rsa_key_size}")
		os.chmod(self.private_key_file, 400)

	def generate_root_certificate(self):
		self.run(
			f"openssl req -new -x509 -days {self.validity_days} -config"
			f" {self.openssl_config_file} -key {self.private_key_file} -out"
			f" {self.certificate_file}"
		)
		os.chmod(self.certificate_file, 444)

	def generate_certificate_signing_request(self):
		self.run(
			f"openssl req -new -config {self.openssl_config_file} -key"
			f" {self.private_key_file} -out {self.certificate_signing_request_file}"
		)
		os.chmod(self.certificate_signing_request_file, 444)

	def sign_certificate_signing_request(self):
		parent = jingrow.get_pg(self.pagetype, self.parent_authority)
		self.run(
			f"openssl ca -batch -notext -days {self.validity_days} -config"
			f" {parent.openssl_config_file} -extensions v3_intermediate_ca -in"
			f" {self.certificate_signing_request_file} -out {self.certificate_file}"
		)
		os.chmod(self.certificate_file, 444)

	def generate_chain_certificate(self):
		parent = jingrow.get_pg(self.pagetype, self.parent_authority)
		with open(self.certificate_file) as f:
			certificate = f.read()
		with open(parent.certificate_file) as f:
			certificate += f.read()
		with open(self.certificate_chain_file, "w") as f:
			f.write(certificate)
		os.chmod(self.certificate_chain_file, 444)

	def extract_certificate_details(self):
		with open(self.certificate_file) as f:
			certificate = f.read()

		x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, certificate)
		self.decoded_certificate = OpenSSL.crypto.dump_certificate(
			OpenSSL.crypto.FILETYPE_TEXT, x509
		).decode()

		self.issued_on = datetime.strptime(x509.get_notBefore().decode(), "%Y%m%d%H%M%SZ")
		self.expires_on = datetime.strptime(x509.get_notAfter().decode(), "%Y%m%d%H%M%SZ")

	def on_trash(self):
		if os.path.exists(self.directory):
			shutil.rmtree(self.directory)
		children = jingrow.get_all(self.pagetype, {"parent_authority": self.name})
		for child in children:
			jingrow.delete_pg(self.pagetype, child.name)

	@property
	def certificate_chain_file(self):
		return os.path.join(self.directory, "chain.pem")

	@property
	def certificate_file(self):
		return os.path.join(self.directory, "cert.pem")

	@property
	def certificate_signing_request_file(self):
		return os.path.join(self.directory, "csr.pem")

	@property
	def database_file(self):
		return os.path.join(self.directory, "index.txt")

	@property
	def new_certificates_directory(self):
		return os.path.join(self.directory, "newcerts")

	@property
	def openssl_config_file(self):
		return os.path.join(self.directory, "openssl.conf")

	@property
	def private_key_file(self):
		return os.path.join(self.directory, "key.pem")

	@property
	def serial_file(self):
		return os.path.join(self.directory, "serial")
