// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::{sync::Arc, net::IpAddr, str::FromStr};

use rustls::{Error, DigitallySignedStruct, SignatureScheme, ClientConfig as RustlsClientConfig, client::danger::{ServerCertVerifier, ServerCertVerified, HandshakeSignatureValid}};
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
use rustls::crypto::{self, aws_lc_rs, CryptoProvider};
use rustls_platform_verifier::ConfigVerifierExt;
use quinn::{ClientConfig, crypto::rustls::QuicClientConfig};
use sysinfo::Networks;

use super::{IdleConfig, IpClassify, IpCategory};

// This struct is copied from https://github.com/quinn-rs/quinn/blob/main/quinn/examples/insecure_connection.rs
#[derive(Debug)]
struct SkipServerVerification(Arc<CryptoProvider>);

impl SkipServerVerification {
	fn new() -> Arc<Self> {
		Arc::new(Self(Arc::new(aws_lc_rs::default_provider())))
	}
}

impl ServerCertVerifier for SkipServerVerification {
	fn verify_server_cert(&self, _end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _server_name: &ServerName<'_>, _ocsp: &[u8], _now: UnixTime) -> Result<ServerCertVerified, Error> {
		Ok(ServerCertVerified::assertion())
	}

	fn verify_tls12_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct) -> Result<HandshakeSignatureValid, Error> {
		crypto::verify_tls12_signature(message, cert, dss, &self.0.signature_verification_algorithms)
	}

	fn verify_tls13_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct) -> Result<HandshakeSignatureValid, Error> {
		crypto::verify_tls13_signature(message, cert, dss, &self.0.signature_verification_algorithms)
	}

	fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
		self.0.signature_verification_algorithms.supported_schemes()
	}
}

/**
 * Creates an insecure `ClientConfig` which will skip verification of the
 * certificate provided by the server. Encryption still happens using this
 * config and passive eavesdropping cannot read any sent messages, but active
 * eavesdropping (a MiTM attack involving a malicious node changing the messages
 * sent) can allow a malicious node to read and modify messages.
 */
pub(super) fn new_insecure(idle_config: IdleConfig) -> Result<ClientConfig, String> {
	// Copied from https://quinn-rs.github.io/quinn/quinn/certificate.html
	from_rustls_config(RustlsClientConfig::builder()
		.dangerous()
		.with_custom_certificate_verifier(SkipServerVerification::new())
		.with_no_client_auth(), idle_config)
}

/**
 * Creates a secure `ClientConfig` that uses the OS's root certificates.
 */
pub(super) fn new_secure(idle_config: IdleConfig) -> Result<ClientConfig, String> {
	from_rustls_config(RustlsClientConfig::with_platform_verifier().map_err(|err| format!("failed getting platform verifier: {err}"))?, idle_config)
}

fn from_rustls_config(config: RustlsClientConfig, idle_config: IdleConfig) -> Result<ClientConfig, String> {
	let mut config = ClientConfig::new(Arc::new(QuicClientConfig::try_from(Arc::new(config)).map_err(|err| format!("cannot create QUIC client config: {err}"))?));
	config.transport_config(super::transport_config(idle_config));
	Ok(config)
}

/**
 * A struct that provides a way to determine whether the connection needs to be
 * secure or not.
 *
 * The purpose of this struct is that this game uses the QUIC transport layer
 * protocol for networking, which has encryption via TLS built into it. One
 * requirement for my game is for things to work completely fine when playing on
 * the local network, with users not needing to somehow configure TLS
 * certificates.
 *
 * However, encryption by default is a nice feature when it comes to playing on
 * the internet, as the messages sent between the client and server aren't the
 * business of a nosey network administrator, a greedy ISP wanting to exploit
 * their users by selling their data to advertisers, or certain three-letter
 * agencies such as the NSA.
 *
 * One possibly policy is to skip checking the server's certificate when that
 * server's destination IP address isn't on the public internet. A problem with
 * this is that a user might want to self-host their own server on the internet
 * using port forwarding to play with some friends.
 *
 * In that case, the user hosting the server shouldn't be expected to spend
 * money to get their certificate trusted (using Let's Encrypt requires the
 * client have a domain name, which would be an additional cost if they don't
 * have one already). They also shouldn't be expected to get their friends to
 * make modifications to the game or system configuration to allow the server's
 * certificate (or possibly disable certificate verification in the game with a
 * setting that could be implemented), as that's too much of a pain in the arse
 * for both me as the developer and those users.
 *
 * My solution is to use this proposed policy, but weaken it to skip certificate
 * verification if the user manually entered an IP address rather than providing
 * a domain name.
 *
 * This would cover the case in which a user chooses to port forward, as they
 * would either manually enter an IP address (which would work), or already have
 * a domain name, which they could use certbot and easily obtain a trusted
 * certificate.
 */
pub(super) struct SecurityPolicy {
	manually_entered_ip_address: bool,
	networks: Option<Networks>,
}

impl SecurityPolicy {
	/**
	 * Creates a new security policy.
	 *
	 * `addr` is the input address that was supplied by the user, or
	 * automatically chosen from LAN discovery (or whatever list of online
	 * servers I provide in the future).
	 */
	pub fn new(input_addr: &str) -> SecurityPolicy {
		SecurityPolicy {
			/*
			 * Note that when the IP address includes a scope id, parsing the IP
			 * address would fail so this condition would be false. However, LAN on
			 * that IP address would still have security disabled as the used
			 * address would be a link-local one, which security isn't required
			 * for.
			 */
			manually_entered_ip_address: IpAddr::from_str(input_addr).is_ok(),
			networks: None,
		}
	}

	/**
	 * Checks whether a secure connection should be used for the given
	 * destination IP address.
	 *
	 * This method can be called multiple times for a given input address if the
	 * user supplied a domain name which resolves to multiple IP addresses.
	 *
	 * Returns true if the secure configuration must be used. Returns false if
	 * the insecure configuration can be used.
	 */
	pub fn needs_security(&mut self, ip_addr: IpAddr) -> bool {
		!self.manually_entered_ip_address && self.networks.classify(ip_addr) == IpCategory::Global
	}

	pub fn classifier(&mut self) -> &mut impl IpClassify {
		&mut self.networks
	}
}
