package Gniza4cpWHM::Validator;
# Input validation mirroring lib/config.sh validate_config()
# and lib/remotes.sh validate_remote().

use strict;
use warnings;

# validate_main_config(\%data, $has_remotes)
# Returns arrayref of error strings (empty = valid).
sub validate_main_config {
    my ($data) = @_;
    my @errors;

    if (defined $data->{NOTIFY_ON} && $data->{NOTIFY_ON} ne '') {
        unless ($data->{NOTIFY_ON} =~ /^(always|failure|never)$/) {
            push @errors, 'NOTIFY_ON must be always, failure, or never';
        }
    }

    if (defined $data->{LOG_LEVEL} && $data->{LOG_LEVEL} ne '') {
        unless ($data->{LOG_LEVEL} =~ /^(debug|info|warn|error)$/) {
            push @errors, 'LOG_LEVEL must be debug, info, warn, or error';
        }
    }

    if (defined $data->{LOG_RETAIN} && $data->{LOG_RETAIN} ne '') {
        push @errors, _validate_positive_int('LOG_RETAIN', $data->{LOG_RETAIN});
    }

    if (defined $data->{SSH_TIMEOUT} && $data->{SSH_TIMEOUT} ne '') {
        push @errors, _validate_non_negative_int('SSH_TIMEOUT', $data->{SSH_TIMEOUT});
    }

    if (defined $data->{SSH_RETRIES} && $data->{SSH_RETRIES} ne '') {
        push @errors, _validate_positive_int('SSH_RETRIES', $data->{SSH_RETRIES});
    }

    if (defined $data->{INCLUDE_ACCOUNTS} && $data->{INCLUDE_ACCOUNTS} ne '') {
        if ($data->{INCLUDE_ACCOUNTS} !~ /^[a-zA-Z0-9_, ]*$/) {
            push @errors, 'INCLUDE_ACCOUNTS contains invalid characters';
        }
    }

    if (defined $data->{EXCLUDE_ACCOUNTS} && $data->{EXCLUDE_ACCOUNTS} ne '') {
        if ($data->{EXCLUDE_ACCOUNTS} !~ /^[a-zA-Z0-9_, ]*$/) {
            push @errors, 'EXCLUDE_ACCOUNTS contains invalid characters';
        }
    }

    if (defined $data->{RSYNC_EXTRA_OPTS} && $data->{RSYNC_EXTRA_OPTS} ne '') {
        if ($data->{RSYNC_EXTRA_OPTS} !~ /^[a-zA-Z0-9 ._=\/-]*$/) {
            push @errors, 'RSYNC_EXTRA_OPTS contains invalid characters';
        }
    }

    if (defined $data->{TEMP_DIR} && $data->{TEMP_DIR} ne '') {
        if ($data->{TEMP_DIR} !~ /^\/[\w\/.+-]*$/) {
            push @errors, 'TEMP_DIR must be an absolute path';
        }
    }

    if (defined $data->{LOG_DIR} && $data->{LOG_DIR} ne '') {
        if ($data->{LOG_DIR} !~ /^\/[\w\/.+-]*$/) {
            push @errors, 'LOG_DIR must be an absolute path';
        }
    }

    if (defined $data->{LOCK_FILE} && $data->{LOCK_FILE} ne '') {
        if ($data->{LOCK_FILE} !~ /^\/[\w\/.+-]*$/) {
            push @errors, 'LOCK_FILE must be an absolute path';
        }
    }

    # SMTP validation (only when SMTP_HOST is set)
    if (defined $data->{SMTP_HOST} && $data->{SMTP_HOST} ne '') {
        if ($data->{SMTP_HOST} !~ /^[a-zA-Z0-9._-]+$/) {
            push @errors, 'SMTP_HOST contains invalid characters';
        }

        if (defined $data->{SMTP_PORT} && $data->{SMTP_PORT} ne '') {
            if ($data->{SMTP_PORT} !~ /^\d+$/ || $data->{SMTP_PORT} < 1 || $data->{SMTP_PORT} > 65535) {
                push @errors, 'SMTP_PORT must be 1-65535';
            }
        }

        if (defined $data->{SMTP_SECURITY} && $data->{SMTP_SECURITY} ne '') {
            unless ($data->{SMTP_SECURITY} =~ /^(tls|ssl|none)$/) {
                push @errors, 'SMTP_SECURITY must be tls, ssl, or none';
            }
        }

        if (defined $data->{SMTP_FROM} && $data->{SMTP_FROM} ne '') {
            unless ($data->{SMTP_FROM} =~ /^[^\s@]+\@[^\s@]+\.[^\s@]+$/) {
                push @errors, 'SMTP_FROM must be a valid email address';
            }
        }
    }

    # Validate NOTIFY_EMAIL addresses (comma-separated)
    if (defined $data->{NOTIFY_EMAIL} && $data->{NOTIFY_EMAIL} ne '') {
        my @addrs = split /\s*,\s*/, $data->{NOTIFY_EMAIL};
        for my $addr (@addrs) {
            next if $addr eq '';
            unless ($addr =~ /^[^\s@]+\@[^\s@]+\.[^\s@]+$/) {
                push @errors, "Invalid email address: $addr";
            }
        }
    }

    if (defined $data->{USER_RESTORE_REMOTES} && $data->{USER_RESTORE_REMOTES} ne '') {
        unless ($data->{USER_RESTORE_REMOTES} eq 'all' || $data->{USER_RESTORE_REMOTES} =~ /^[a-zA-Z0-9_,-]+$/) {
            push @errors, 'USER_RESTORE_REMOTES must be "all" or comma-separated remote names';
        }
    }

    # Filter out empty strings from helper returns
    return [grep { $_ ne '' } @errors];
}

# validate_remote_config(\%data)
# Returns arrayref of error strings (empty = valid).
sub validate_remote_config {
    my ($data) = @_;
    my @errors;

    my $type = $data->{REMOTE_TYPE} // 'ssh';
    unless ($type =~ /^(ssh|s3|gdrive)$/) {
        push @errors, 'REMOTE_TYPE must be ssh, s3, or gdrive';
        return [grep { $_ ne '' } @errors];
    }

    # Common validations
    push @errors, _validate_positive_int('RETENTION_COUNT', $data->{RETENTION_COUNT});
    push @errors, _validate_non_negative_int('BWLIMIT', $data->{BWLIMIT});

    if (defined $data->{REMOTE_BASE} && $data->{REMOTE_BASE} ne '') {
        if ($data->{REMOTE_BASE} !~ /^\/[\w\/.+-]*$/) {
            push @errors, 'REMOTE_BASE must be an absolute path';
        }
    }

    if ($type eq 'ssh') {
        # SSH-specific validation
        if (!defined $data->{REMOTE_HOST} || $data->{REMOTE_HOST} eq '') {
            push @errors, 'REMOTE_HOST is required';
        } elsif ($data->{REMOTE_HOST} !~ /^[a-zA-Z0-9._-]+$/) {
            push @errors, 'REMOTE_HOST contains invalid characters';
        }

        my $auth_method = $data->{REMOTE_AUTH_METHOD} // 'key';
        if ($auth_method ne 'key' && $auth_method ne 'password') {
            push @errors, 'REMOTE_AUTH_METHOD must be key or password';
        }

        if ($auth_method eq 'password') {
            if (!defined $data->{REMOTE_PASSWORD} || $data->{REMOTE_PASSWORD} eq '') {
                push @errors, 'REMOTE_PASSWORD is required for password authentication';
            }
        } else {
            if (!defined $data->{REMOTE_KEY} || $data->{REMOTE_KEY} eq '') {
                push @errors, 'REMOTE_KEY is required';
            } elsif ($data->{REMOTE_KEY} !~ /^\/[\w\/.+-]+$/) {
                push @errors, 'REMOTE_KEY must be an absolute path';
            } elsif (!-f $data->{REMOTE_KEY}) {
                push @errors, "REMOTE_KEY file not found: $data->{REMOTE_KEY}";
            }
        }

        push @errors, _validate_port($data->{REMOTE_PORT});

        if (defined $data->{REMOTE_USER} && $data->{REMOTE_USER} ne '') {
            if ($data->{REMOTE_USER} !~ /^[a-z_][a-z0-9_-]*$/) {
                push @errors, 'REMOTE_USER contains invalid characters';
            }
        }

        if (defined $data->{RSYNC_EXTRA_OPTS} && $data->{RSYNC_EXTRA_OPTS} ne '') {
            if ($data->{RSYNC_EXTRA_OPTS} !~ /^[a-zA-Z0-9 ._=\/-]*$/) {
                push @errors, 'RSYNC_EXTRA_OPTS contains invalid characters';
            }
        }
    }
    elsif ($type eq 's3') {
        if (!defined $data->{S3_ACCESS_KEY_ID} || $data->{S3_ACCESS_KEY_ID} eq '') {
            push @errors, 'S3_ACCESS_KEY_ID is required';
        }
        if (!defined $data->{S3_SECRET_ACCESS_KEY} || $data->{S3_SECRET_ACCESS_KEY} eq '') {
            push @errors, 'S3_SECRET_ACCESS_KEY is required';
        }
        if (!defined $data->{S3_BUCKET} || $data->{S3_BUCKET} eq '') {
            push @errors, 'S3_BUCKET is required';
        } elsif ($data->{S3_BUCKET} !~ /^[a-z0-9][a-z0-9._-]{1,61}[a-z0-9]$/) {
            push @errors, 'S3_BUCKET contains invalid characters';
        }
        if (defined $data->{S3_REGION} && $data->{S3_REGION} ne '') {
            if ($data->{S3_REGION} !~ /^[a-z0-9-]+$/) {
                push @errors, 'S3_REGION contains invalid characters';
            }
        }
    }
    elsif ($type eq 'gdrive') {
        if (!defined $data->{GDRIVE_SERVICE_ACCOUNT_FILE} || $data->{GDRIVE_SERVICE_ACCOUNT_FILE} eq '') {
            push @errors, 'GDRIVE_SERVICE_ACCOUNT_FILE is required';
        } elsif ($data->{GDRIVE_SERVICE_ACCOUNT_FILE} !~ /^\/[\w\/.+-]+$/) {
            push @errors, 'GDRIVE_SERVICE_ACCOUNT_FILE must be an absolute path';
        } elsif (!-f $data->{GDRIVE_SERVICE_ACCOUNT_FILE}) {
            push @errors, "GDRIVE_SERVICE_ACCOUNT_FILE not found: $data->{GDRIVE_SERVICE_ACCOUNT_FILE}";
        }
    }

    return [grep { $_ ne '' } @errors];
}

# validate_remote_name($name)
# Returns error string or empty string if valid.
sub validate_remote_name {
    my ($name) = @_;
    if (!defined $name || $name eq '') {
        return 'Remote name is required';
    }
    if ($name !~ /^[a-zA-Z0-9_-]+$/) {
        return 'Remote name may only contain letters, digits, hyphens, and underscores';
    }
    if (length($name) > 64) {
        return 'Remote name is too long (max 64 characters)';
    }
    return '';
}

# validate_schedule_config(\%data)
# Returns arrayref of error strings (empty = valid).
sub validate_schedule_config {
    my ($data) = @_;
    my @errors;

    my $schedule = $data->{SCHEDULE} // '';
    if ($schedule eq '') {
        push @errors, 'SCHEDULE is required';
    } elsif ($schedule !~ /^(hourly|daily|weekly|monthly|custom)$/) {
        push @errors, 'SCHEDULE must be hourly, daily, weekly, monthly, or custom';
    }

    my $stime = $data->{SCHEDULE_TIME} // '';
    if ($stime ne '' && $stime !~ /^([01]\d|2[0-3]):[0-5]\d$/) {
        push @errors, 'SCHEDULE_TIME must be HH:MM (24-hour format)';
    }

    if ($schedule eq 'hourly') {
        my $sday = $data->{SCHEDULE_DAY} // '';
        if ($sday ne '' && ($sday !~ /^\d+$/ || $sday < 1 || $sday > 23)) {
            push @errors, 'SCHEDULE_DAY must be 1-23 (hours between backups) for hourly schedule';
        }
    } elsif ($schedule eq 'weekly') {
        my $sday = $data->{SCHEDULE_DAY} // '';
        if ($sday eq '' || $sday !~ /^[0-6]$/) {
            push @errors, 'SCHEDULE_DAY must be 0-6 for weekly schedule';
        }
    } elsif ($schedule eq 'monthly') {
        my $sday = $data->{SCHEDULE_DAY} // '';
        if ($sday eq '' || $sday !~ /^\d+$/ || $sday < 1 || $sday > 28) {
            push @errors, 'SCHEDULE_DAY must be 1-28 for monthly schedule';
        }
    } elsif ($schedule eq 'custom') {
        my $scron = $data->{SCHEDULE_CRON} // '';
        if ($scron eq '' || $scron !~ /^[\d*,\/-]+(\s+[\d*,\/-]+){4}$/) {
            push @errors, 'SCHEDULE_CRON must be a valid 5-field cron expression';
        }
    }

    my $remotes = $data->{REMOTES} // '';
    if ($remotes ne '' && $remotes !~ /^[a-zA-Z0-9_,-]+$/) {
        push @errors, 'REMOTES must be comma-separated remote names (letters, digits, hyphens, underscores)';
    }

    return [grep { $_ ne '' } @errors];
}

# validate_schedule_name($name)
# Returns error string or empty string if valid.
sub validate_schedule_name {
    my ($name) = @_;
    if (!defined $name || $name eq '') {
        return 'Schedule name is required';
    }
    if ($name !~ /^[a-zA-Z0-9_-]+$/) {
        return 'Schedule name may only contain letters, digits, hyphens, and underscores';
    }
    if (length($name) > 64) {
        return 'Schedule name is too long (max 64 characters)';
    }
    return '';
}

# -- Private helpers --

sub _validate_port {
    my ($val) = @_;
    $val //= '';
    return '' if $val eq '';
    if ($val !~ /^\d+$/ || $val < 1 || $val > 65535) {
        return 'REMOTE_PORT must be 1-65535';
    }
    return '';
}

sub _validate_positive_int {
    my ($name, $val) = @_;
    $val //= '';
    return '' if $val eq '';
    if ($val !~ /^\d+$/ || $val < 1) {
        return "$name must be a positive integer";
    }
    return '';
}

sub _validate_non_negative_int {
    my ($name, $val) = @_;
    $val //= '';
    return '' if $val eq '';
    if ($val !~ /^\d+$/) {
        return "$name must be a non-negative integer";
    }
    return '';
}

1;
