package Devscripts::Salsa::file;

use strict;
use Devscripts::Output;
use Moo::Role;
use URI::Escape;

with 'Devscripts::Salsa::Repo';

sub file {
    my ($self, $command, $project_name, $file_path, @args) = @_;

    unless ($project_name && $file_path) {
        ds_die
"Usage: salsa file <get|create|edit|delete|create_or_edit> <project_name> <file_path> [options]";
    }

    my @repos = $self->get_repo(0, $project_name);
    return 1 unless (ref $repos[0]);
    my ($id, $str) = @{ $repos[0] };
    if (!$id) {
        ds_warn "Project not found: $project_name\n";
        return 1;
    }

    my $branch            = $self->config->branch;
    my $ref               = $self->config->ref;
    my $commit_message    = $self->config->commit_message;
    my $content_from_file = $self->config->content_from_file;
    my $content;

    if ($content_from_file) {
        if (open(my $fh, '<:utf8', $content_from_file)) {
            $content = do { local $/; <$fh> };
            close($fh);
        } else {
            ds_die "Could not open file '$content_from_file' for reading: $!";
        }
    }

    # Encode file_path for URL
    my $encoded_file_path = uri_escape($file_path, qr{^a-zA-Z0-9\-\._~/});

    my @content_required = qw(create edit create_or_edit);
    if (grep { $_ eq $command } @content_required) {
        unless (defined $content) {
            ds_die
"Content is required for '$command' command. Use --content-from-file.";
        }
    }

    if ($command eq 'get') {
        return $self->_get_file($id, $encoded_file_path, $ref);
    } elsif ($command eq 'create') {
        return $self->_file_action('create', $id, $encoded_file_path,
            $content, $branch, $commit_message);
    } elsif ($command eq 'edit') {
        return $self->_file_action('edit', $id, $encoded_file_path, $content,
            $branch, $commit_message);
    } elsif ($command eq 'delete') {
        return $self->_file_action('delete', $id, $encoded_file_path, undef,
            $branch, $commit_message);
    } elsif ($command eq 'create_or_edit') {
        return $self->_create_or_edit($id, $encoded_file_path, $content,
            $branch, $commit_message);
    } else {
        ds_die
"Unknown subcommand: $command. Use get, create, edit, delete, or create_or_edit.";
    }
}

sub _file_action {
    my ($self, $action, $project_id, $file_path, $content, $branch,
        $commit_message)
      = @_;

    my $api_method = $action . '_file';
    $commit_message ||= ucfirst($action) . " $file_path via salsa";
    $branch         ||= $self->api->project($project_id)->{default_branch};

    my %api_params = (
        branch         => $branch,
        commit_message => $commit_message,
    );
    $api_params{content} = $content if defined $content;

    eval {
        $self->api->$api_method($project_id, $file_path, \%api_params,);
        my %past_tense = (
            create => 'created',
            edit   => 'edited',
            delete => 'deleted',
        );
        my $past_tense_action = $past_tense{$action} || $action;

        ds_msg
"File '$file_path' $past_tense_action successfully on branch '$branch'.\n";
    };
    if ($@) {
        ds_warn "Failed to $action file: $@";
        return 1;
    }
    return 0;
}

sub _get_file {
    my ($self, $project_id, $file_path, $ref) = @_;
    my $ret = 0;    # Assume success
    eval {
        my $params = {};
        $params->{ref} = $ref if $ref;
        my $file = $self->api->raw_file($project_id, $file_path, $params);
        if (defined $file) {
            print $file;
        } else {
            ds_warn "File '$file_path' not found in ref '"
              . ($ref || 'default branch') . "'.\n";
            $ret = 1;    # Set return value to 1 for failure
        }
    };
    if ($@) {
        ds_warn "Failed to get file: $@";
        $ret = 1;        # Set return value to 1 for failure
    }
    return $ret;         # Return the stored result
}

sub _create_or_edit {
    my ($self, $project_id, $file_path, $content, $branch, $commit_message)
      = @_;

    # Try to get the file to check if it exists
    my $file_exists = 0;
    eval {
        my $file = $self->api->raw_file(
            $project_id,
            $file_path,
            {
                ref => $branch
                  || $self->api->project($project_id)->{default_branch} });
        if (defined $file) {
            $file_exists = 1;
        }
    };
    # If eval dies, it means file does not exist or some other error.
    # We only care if it exists or not. If it dies, assume it doesn't exist.

    if ($file_exists) {
        ds_msg "File '$file_path' exists, updating...\n";
        return $self->_file_action('edit', $project_id, $file_path, $content,
            $branch, $commit_message);
    } else {
        ds_msg "File '$file_path' does not exist, creating...\n";
        return $self->_file_action('create', $project_id, $file_path,
            $content, $branch, $commit_message);
    }
}

1;

=head1 NAME

Devscripts::Salsa::file - Manipulate repository files on Salsa

=head1 DESCRIPTION

This module provides subcommands to interact with repository files on GitLab (Salsa).

=head1 COMMANDS

=head2 B<file get> <project_name> <file_path> [--ref <branch/tag/commit>]

Retrieves the content of a file from a repository.

=over

=item B<project_name>

The name or ID of the project (e.g., 'debian/devscripts').

=item B<file_path>

The path to the file within the repository (e.g., 'README.md').

=item B<--ref> <branch/tag/commit>

Optional. The branch, tag, or commit SHA to retrieve the file from.
Defaults to the project's default branch.

=back

=head2 B<file create> <project_name> <file_path> --content-from-file <file_path> [--branch <branch_name>] [--commit-message <message>]

Creates a new file in a repository.

=over

=item B<project_name>

The name or ID of the project.

=item B<file_path>

The path to the new file within the repository.

=item B<--content-from-file> <file_path>

The path to the file containing the content to write to the new file.

=item B<--branch> <branch_name>

Optional. The branch to create the file on. Defaults to the project's default branch.

=item B<--commit-message> <message>

Optional. The commit message for the creation. Defaults to "Add <file_path> via salsa".

=back

=head2 B<file edit> <project_name> <file_path> --content-from-file <file_path> [--branch <branch_name>] [--commit-message <message>]

Edits an existing file in a repository.

=over

=item B<project_name>

The name or ID of the project.

=item B<file_path>

The path to the file to edit within the repository.

=item B<--content-from-file> <file_path>

The path to the file containing the new content for the file.

=item B<--branch> <branch_name>

Optional. The branch to edit the file on. Defaults to the project's default branch.

=item B<--commit-message> <message>

Optional. The commit message for the edit. Defaults to "Edit <file_path> via salsa".

=back

=head2 B<file delete> <project_name> <file_path> [--branch <branch_name>] [--commit-message <message>]

Deletes a file from a repository.

=over

=item B<project_name>

The name or ID of the project.

=item B<file_path>

The path to the file to delete within the repository.

=item B<--branch> <branch_name>

Optional. The branch to delete the file from. Defaults to the project's default branch.

=item B<--commit-message> <message>

Optional. The commit message for the deletion. Defaults to "Delete <file_path> via salsa".

=back

=head2 B<file create_or_edit> <project_name> <file_path> --content-from-file <file_path> [--branch <branch_name>] [--commit-message <message>]

Creates a new file or edits an existing file in a repository.

=over

=item B<project_name>

The name or ID of the project.

=item B<file_path>

The path to the file within the repository.

=item B<--content-from-file> <file_path>

The path to the file containing the content to write to the file.

=item B<--branch> <branch_name>

Optional. The branch to create or edit the file on. Defaults to the project's default branch.

=item B<--commit-message> <message>

Optional. The commit message for the creation or edit. Defaults to a suitable message depending on the action (create or edit).

=back

=cut
