// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// TLS related error
#derive(@debug.Debug, @json.ToJson)
suberror TlsError {
  TlsError(String)
}

///|
pub impl Show for TlsError with fn to_string(self) {
  let TlsError(msg) = self
  msg
}

///|
pub extend TlsError with Show::{to_string}

///|
/// Error raised when the peer abnormally close the underlying transport
/// in a TLS connection.
#derive(@debug.Debug, @json.ToJson)
pub suberror ConnectionClosed

///|
const PEM_BEGIN = "-----BEGIN CERTIFICATE-----"

///|
const PEM_END = "-----END CERTIFICATE-----"

///|
fn decode_pem_certificates(pem : String) -> Array[Bytes] raise TlsError {
  let certs = []
  for chunk in pem.split(PEM_BEGIN) {
    match chunk.split_once(PEM_END) {
      None => ()
      Some((base64_body, _)) => {
        let cert = @base64.decode(base64_body.trim(), ignore_whitespace=true) catch {
          _ =>
            raise TlsError(
              "invalid PEM certificate in custom root certificate file",
            )
        }
        certs.push(cert)
      }
    }
  }
  guard !certs.is_empty() else {
    raise TlsError("no PEM certificate found in custom root certificate file")
  }
  certs
}

///|
/// Encoding type for a X509 certificate file
pub(all) enum X509FileType {
  PEM = 1
  ANS1 = 2
}

///|
/// Create a TLS client that read from write to `inner`.
/// `client` will block until TLS handshake to remote server completed.
///
/// `trust` specifies which servers are trusted and how certificate validation is performed.
/// See `TrustedRoot` for more details. The default is `SystemRoot`.
///
/// If `host` is present, it will be used to verify the peer's certificate.
///
/// If `host` is present and `sni` is `true` (`true` by default),
/// Server Name Indication (SNI) field of TLS will be set to `host`.
/// `sni=false` is currently unsupported on Windows.
pub async fn[Inner : @io.BinaryReader + @io.BinaryWriter] Tls::client(
  inner : Inner,
  host? : String,
  sni~ : Bool = true,
  trust? : TrustedRoot,
) -> Tls {
  Tls::client_from_pair(inner, inner, host?, sni~, trust?)
}

///|
pub impl @io.BinaryReader for Tls

///|
pub extend Tls with @io.BinaryReader::{
  read_into,
  read,
  read_at_least,
  peek,
  peek_at_least,
  read_exactly,
  drop_exactly,
  peek_exactly,
  read_all,
  drop_all,
  read_until,
  peek_until,
}

///|
pub impl @io.BinaryWriter for Tls

///|
pub extend Tls with @io.BinaryWriter::{write, write_reader}