package Gniza4cpWHM::Config;
# Pure Perl config file parser/writer for bash-style KEY="value" files.
# No shell calls — reads/writes via Perl file I/O only.

use strict;
use warnings;
use Fcntl qw(:flock);

our @MAIN_KEYS = qw(
    TEMP_DIR INCLUDE_ACCOUNTS EXCLUDE_ACCOUNTS
    RSYNC_EXTRA_OPTS LOG_DIR LOG_LEVEL LOG_RETAIN NOTIFY_EMAIL NOTIFY_ON
    SMTP_HOST SMTP_PORT SMTP_USER SMTP_PASSWORD SMTP_FROM SMTP_SECURITY
    LOCK_FILE SSH_TIMEOUT SSH_RETRIES
    USER_RESTORE_REMOTES
);

our @REMOTE_KEYS = qw(
    REMOTE_TYPE REMOTE_HOST REMOTE_PORT REMOTE_USER REMOTE_AUTH_METHOD REMOTE_KEY
    REMOTE_PASSWORD REMOTE_BASE BWLIMIT RETENTION_COUNT RSYNC_EXTRA_OPTS
    S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY S3_REGION S3_ENDPOINT S3_BUCKET
    GDRIVE_SERVICE_ACCOUNT_FILE GDRIVE_ROOT_FOLDER_ID
);

our @SCHEDULE_KEYS = qw(
    SCHEDULE SCHEDULE_TIME SCHEDULE_DAY SCHEDULE_CRON REMOTES SYSBACKUP SKIP_SUSPENDED
);

my %MAIN_KEY_SET     = map { $_ => 1 } @MAIN_KEYS;
my %REMOTE_KEY_SET   = map { $_ => 1 } @REMOTE_KEYS;
my %SCHEDULE_KEY_SET = map { $_ => 1 } @SCHEDULE_KEYS;

# parse($filepath, $type)
# $type: 'main', 'remote', or 'schedule' — determines which keys are allowed.
# Returns hashref of KEY => value.
sub parse {
    my ($filepath, $type) = @_;
    $type //= 'main';
    my $allowed = ($type eq 'schedule') ? \%SCHEDULE_KEY_SET
               : ($type eq 'remote')   ? \%REMOTE_KEY_SET
               :                          \%MAIN_KEY_SET;

    my %config;
    open my $fh, '<', $filepath or return \%config;  ## no critic (RequireBriefOpen)
    while (my $line = <$fh>) {
        chomp $line;
        # Skip blank lines and comments
        next if $line =~ /^\s*$/;
        next if $line =~ /^\s*#/;
        # Match KEY="value", KEY='value', or KEY=value
        if ($line =~ /^([A-Z_]+)=(?:"([^"]*)"|'([^']*)'|(\S*))$/) {
            my $key = $1;
            my $val = defined $2 ? $2 : (defined $3 ? $3 : ($4 // ''));
            if ($allowed->{$key}) {
                $config{$key} = $val;
            }
        }
    }
    close $fh;
    return \%config;
}

# escape_value($string)
# Strips everything except safe characters for bash config values.
sub escape_value {
    my ($val) = @_;
    $val //= '';
    $val =~ s/[^a-zA-Z0-9\@._\/: ,=+\-]//g;
    return $val;
}

# Keys whose values are written with single quotes (preserves special chars).
my %SINGLE_QUOTE_KEYS = (REMOTE_PASSWORD => 1, S3_SECRET_ACCESS_KEY => 1, SMTP_PASSWORD => 1);

# escape_password($string)
# For single-quoted bash values: only strip single quotes (can't appear in single-quoted strings).
sub escape_password {
    my ($val) = @_;
    $val //= '';
    $val =~ s/'//g;
    return $val;
}

# save($filepath, \%values, \@allowed_keys)
# Updates a config file preserving comments and structure.
# Keys not in @allowed_keys are ignored. Values are escaped.
# Uses flock for concurrency safety.
sub save {
    my ($filepath, $values, $allowed_keys) = @_;

    my %allowed = map { $_ => 1 } @$allowed_keys;
    my %to_write;
    for my $key (keys %$values) {
        if ($allowed{$key}) {
            $to_write{$key} = $SINGLE_QUOTE_KEYS{$key}
                ? escape_password($values->{$key})
                : escape_value($values->{$key});
        }
    }

    # Open file for read+write with exclusive lock to prevent TOCTOU
    ## no critic (RequireBriefOpen)
    my @lines;
    my $wfh;
    if (-f $filepath) {
        open $wfh, '+<', $filepath or return (0, "Cannot open $filepath: $!");
        flock($wfh, LOCK_EX) or return (0, "Cannot lock $filepath: $!");
        @lines = <$wfh>;
    } else {
        open $wfh, '>', $filepath or return (0, "Cannot create $filepath: $!");
        flock($wfh, LOCK_EX) or return (0, "Cannot lock $filepath: $!");
    }

    # Track which keys we've updated in-place
    my %written;
    my @output;
    for my $line (@lines) {
        if ($line =~ /^([A-Z_]+)=/) {
            my $key = $1;
            if (exists $to_write{$key}) {
                my $val = $to_write{$key};
                my $q = $SINGLE_QUOTE_KEYS{$key} ? "'" : '"';
                push @output, "$key=$q$val$q\n";
                $written{$key} = 1;
                next;
            }
        }
        push @output, $line;
    }

    # Append any new keys not already in the file
    for my $key (@$allowed_keys) {
        next unless exists $to_write{$key};
        next if $written{$key};
        my $val = $to_write{$key};
        my $q = $SINGLE_QUOTE_KEYS{$key} ? "'" : '"';
        push @output, "$key=$q$val$q\n";
    }

    # Truncate and write under the same lock
    seek($wfh, 0, 0) or return (0, "Cannot seek $filepath: $!");
    truncate($wfh, 0) or return (0, "Cannot truncate $filepath: $!");
    print $wfh @output;
    close $wfh;

    return (1, undef);
}

1;
