#!/usr/bin/env perl

use Object::Pad ':experimental(:all)';

package Net::SSLeay::CA::catool;

class Net::SSLeay::CA::catool : does(Net::SSLeay::CA::Base);

use utf8;
use v5.40;

use lib 'lib';

use Time::Moment;
use Time::Piece;
use Time::Seconds;
use Pod::Usage qw(pod2usage);
use Const::Fast;
use Data::Dumper::Names;
use Net::Domain;
use Getopt::Long
  qw(GetOptionsFromArray HelpMessage :config no_ignore_case bundling auto_abbrev);

use Net::SSLeay;
use Net::SSLeay::CA::Util;
use Net::SSLeay::CA::Strings;
use Net::SSLeay::CA::Util::Cmd;

const our $DEBUG => $ENV{DEBUG} // 0;
const our $OPENSSL_KEYSPEC =>
  ( 'keyalgo|key-algorithm=s', 'keybits|key-bits=s' );
const our $OPENSSL_CERTSPEC => (

    'csr|in=s'
    , # Would this evver be anything other than a CSR? Possibly just a list of files we later detect the purpose of? x509toreq would need a certifiate input rather than a CSR. And of course there are already options for config files we'll proabably pass through to openssl
    'CAcert|signing-certfile|signature-certfile=s',
    'CAkey=s|signing-keyfile=s|signature-keyfile=s',

    'cert|certfile=s',
    'key|keyfile=s',

    # 'clientauth-certfile=s',
    # 'clientauth-keyfile=s'
    #

    # 'signingCAcertfile=s',
    # 'signingCAkeyfile=s',

    'subject=s',
    'cn|commonname=s',
    'org|organization=s',
    'ou|organizational-unit|',
    'days|daysvalid|days-valid|duration|validfor|valid-for=s',

);

field $argv;
field $clispec;
field $cliopt;
field $debug   = $DEBUG;
field $openssl = map { chomp $_; $_ } (`which openssl`);
field $CAtop;

field $csr;
field $CAcert;
field $CAkey;
field $certfile;
field $keyfile;

field $class = "Local";
field $iter  = 1;
field $issuer;
field $pathlen = 0;
field $SAN;

field @days = Time::Seconds->new( ONE_YEAR * 5 )->days;

field $keyalgo = 'RSA';
field $keybits = 4096;

field $hostfqdn  { $self->hostfqdn };
field $localuser { ( $self->localuser ) };

my class Subject {
    field $cn : param : mutator;
    field $o  : param : mutator;
    field $ou : param : mutator;

    method toASN1 {
        my ( %defined, $asn1 );
        foreach my ( $k, $v ) ( CN => $cn, O => $o, OU => $ou ) {
            $defined{$k} = $v;
        }
        $asn1 = join '/', map { "$_=$defined{$_}" } keys %defined;
    }
};

field $subject : reader {
    Subject->new(
        cn => "$localuser\@$hostfqdn $class CA$iter",
        o  => "$hostfqdn",
        ou => "Local User"
    )
};

ADJUST {
    $self->dmsg($self)
}

method $cmd {
    %Net::SSLeay::CA::Cmd::;
}

method localuser ( $user //= getpwent ) {
    $localuser //= $user;
    $localuser;
}

method hostfqdn ( $fqdn =  Net::Domain::hostfqdn ) {
$hostfqdn //= $fqdn;
    $hostfqdn;
}

method valid_days ( $str, %opt ) {
    const my $durunit_re => qr/^[0-9]+([a-z]{1,3})$/i;

    if ( my ( $dur, $unit ) = $str =~ $durunit_re ) {

    }
}

method x509toreq {

# x509args=(-in "$templateCA" -x509toreq
#     -out "$CAtop/$newCAcsr" \
#     -set_subject "/CN=$cnsubj/O=$hostfqdn/OU=${SUBJ_ON:-Local User}/C=${SUBJ_C:-US}/" \
#     -key "$CAtop/private/$newCAkey"
# )

    # [[ -n "$CLREXT" ]] && x509args+=(-clrext)
    # [[ -n "$EXTFILE" ]] && x509args+=(-extfile "$EXTFILE")
    # [[ -n "$EXTENSIONS" ]] && x509args+=(-extensions "$EXTENSIONS")
    # [[ -n "$OPENSSL_CONFIG" ]] && x509args+=(-config "$OPENSSL_CONFIG")

    # openssl x509 "${x509args[@]}"
}

method newCA {
    GetOptionsFromArray(
        $argv,
        #'CAcert=s',
        #'cert|certfile=s',
        #'CAkey=s',
        #'key|keyfile=s',

        # 'signingCAcertfile=s',
        # 'signingCAkeyfile=s',
        #'csr',# 'class',
        # 'subject=s',
        # 'cn|commonname=s',
        # 'org|organization=s',
        # 'ou|organizational-unit|',
        # 'days|daysvalid|days-valid=s',
        # 'keyalgo|key-algorithm=s',
        # 'keybits|key-bits=s',
        'pathlen=s',
        #'san|SAN|subject-alternative-name|subj-alt-name|subjaltname=s',
        <> => sub ($bare) {
            $self->fatal(
"Conflicting options `-cn|commonname` and positional argument (for: subject common name) are both set. Only one can be defined at a time."
            ) if $subject->cn;
            $subject->cn = $bare;
        },
        "help" => sub { HelpMessage() }
    );
}

method CAsign {
    $cmd->run( [ $openssl, 'ca', ] );
}

method signCA {

}

method selfsignCA {

}

method newcert {

}

method newcsr {
GetOptionsFromArray( $argv, )
}

method newreq {
    $self->newcsr(@_);
}

method signcsr {

}

method signreq {
    $self->signcsr(@_);
}

method genpkey {
    $self->info("Generating private key ($keyalgo:$keybits):");

   # pkeyconf=("$keyalgo" "bits:$keybits")
   # openssl genpkey -algorithm "${pkeyconf[*]:0:1}" -pkeyopt "${pkeyconf[*]:1:1
   # -out "$CAtop/private/$newCAkey";
}

method secureboot {

    # Generate/sign which ever keys aren't provided
}

method dmsg {
    local $Data::Dumper::Names::UpLevel = 2;
    $self->info( Dumper(@_) );
}

method help ( $error = "", $exit = ( $? >> 8 || 0 ) ) {
    my $caller = [ caller 0 ];

    $self->err( "$error $$caller[0]:$$caller[1] line " . __LINE__ . "\n" )
      if $error;

    $class->dmsg( { caller => $caller, ( $error ? ( error => $error ) : () ) } )
      if $debug > 1;

    $self->info( pod2usage(2) );

    exit $exit;
}

method run : common  ($argv = \@ARGV, %opt) {
    my $command //= 'help';
    GetOptionsFromArray(
        $argv,
'command|newCA|signCA|newcert|newreq|newcsr|help|signcsr|genpkey|newkey|secureboot|sb',
        'openssl-config=s',
        'config=s',
        'openssl-binary',
        'catop|top|dir|caroot|rootdir=s',

        # "help|?"  => sub { HelpMessage() },
        "version" => sub { VersionMessage() }
    );
    my $self = $class->new(
        command => $command,
        argv    => $argv,
        %opt
    );
    $self->$command;
}

package catool;
use Getopt::Long
  qw(GetOptionsFromArray :config no_ignore_case bundling auto_abbrev);
our $catool = Net::SSLeay::CA::catool->run( \@ARGV );
$catool->dmsg($catool)
