package Devscripts::Salsa::rename_branches;

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

with 'Devscripts::Salsa::Repo';

# Helper function to add a mapping only if the source branch exists.
sub add_mapping_if_exists {
    my ($branch_map_ref, $remote_branch_names_ref, $old, $new) = @_;
    $branch_map_ref->{$old} //= $new
      if exists $remote_branch_names_ref->{$old};
}

sub _add_dep14_mappings {
    my ($self, $project, $branch_map_ref, $remote_branch_names_ref) = @_;

    ds_verbose "Adding DEP-14 default mappings for existing branches.";

    # Core DEP-14 and special branches
    add_mapping_if_exists($branch_map_ref, $remote_branch_names_ref,
        $project->{default_branch},
        'debian/latest');
    add_mapping_if_exists(
        $branch_map_ref, $remote_branch_names_ref,
        'upstream',      'upstream/latest'
    );
    add_mapping_if_exists(
        $branch_map_ref,   $remote_branch_names_ref,
        'upstream/latest', 'upstream/latest'
    );
    add_mapping_if_exists(
        $branch_map_ref, $remote_branch_names_ref,
        'pristine-tar',  'pristine-tar'
    );
    add_mapping_if_exists(
        $branch_map_ref,      $remote_branch_names_ref,
        'patch-queue/master', 'patch-queue/master'
    );
    add_mapping_if_exists(
        $branch_map_ref,       $remote_branch_names_ref,
        'debian/experimental', 'debian/experimental'
    );

    # Debian suites
    my @debian_suites
      = qw(trixie bookworm bullseye buster stretch jessie wheezy);
    for my $suite (@debian_suites) {
        add_mapping_if_exists($branch_map_ref, $remote_branch_names_ref,
            $suite, "debian/$suite");
        add_mapping_if_exists(
            $branch_map_ref,      $remote_branch_names_ref,
            "${suite}-backports", "debian/${suite}-backports"
        );
        add_mapping_if_exists($branch_map_ref, $remote_branch_names_ref,
            "${suite}-backports-staging", "debian/${suite}-backports-staging");
        add_mapping_if_exists(
            $branch_map_ref,      $remote_branch_names_ref,
            "${suite}-fasttrack", "debian/${suite}-fasttrack"
        );
        add_mapping_if_exists(
            $branch_map_ref,     $remote_branch_names_ref,
            "${suite}-security", "debian/${suite}-security"
        );

       # Map new DEP-14 names to themselves to avoid being flagged as unmapped.
        add_mapping_if_exists(
            $branch_map_ref, $remote_branch_names_ref,
            "debian/$suite", "debian/$suite"
        );
        add_mapping_if_exists(
            $branch_map_ref,             $remote_branch_names_ref,
            "debian/${suite}-backports", "debian/${suite}-backports"
        );
        add_mapping_if_exists(
            $branch_map_ref, $remote_branch_names_ref,
            "debian/${suite}-backports-staging",
            "debian/${suite}-backports-staging"
        );
        add_mapping_if_exists(
            $branch_map_ref,             $remote_branch_names_ref,
            "debian/${suite}-fasttrack", "debian/${suite}-fasttrack"
        );
        add_mapping_if_exists(
            $branch_map_ref,            $remote_branch_names_ref,
            "debian/${suite}-security", "debian/${suite}-security"
        );
    }
}

sub rename_branches {
    my ($self, $project_name, @branches) = @_;

    # --- Project Information ---
    # The first argument is the project name.
    # The rest of the arguments are branch mappings in the format "old:new".
    # Get the project ID and path.
    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;
    }
    # Get the full project object.
    my $project = $self->api->project($id);

    # --- Configuration ---
    my $no_wait = $self->config->no_wait;

    # --- Branch Mappings ---
    # The rest of the arguments are branch mappings in the format "old:new".
    my %branch_map;
    for my $branch (@branches) {
        my ($old, $new) = split /:/, $branch, 2;
        $branch_map{$old} = $new;
    }

    # --- DEP-14 Default Mappings ---
    if ($self->config->dep14) {
        my @remote_branches     = @{ $self->api->branches($id) };
        my %remote_branch_names = map { $_->{name} => 1 } @remote_branches;
        $self->_add_dep14_mappings($project, \%branch_map,
            \%remote_branch_names);
    }

    # --- Rename Branches ---
    my $default_branch = $project->{default_branch};
    my %renamed_branches;

    my @remote_branches_list = @{ $self->api->branches($id) };
    my %remote_branch_names_map
      = map { $_->{name} => 1 } @remote_branches_list;

    for my $old (keys %branch_map) {
        my $new = $branch_map{$old};

        if ($old eq $new) {
            ds_verbose "Branch '$old' is mapped to itself, skipping.";
            next;
        }

       # --- Pre-flight checks ---
       # Check if the source branch exists and the destination branch does not.
        my $source_branch;
        my $dest_branch_exists = 0;
        if (exists $remote_branch_names_map{$old}) {
            foreach my $b (@remote_branches_list) {
                if ($b->{name} eq $old) {
                    $source_branch = $b;
                    last;
                }
            }
        }
        $dest_branch_exists = 1 if exists $remote_branch_names_map{$new};

        if (not $source_branch) {
            ds_warn "Source branch '$old' not found, skipping.\n";
            next;
        }

        if ($dest_branch_exists) {
            ds_warn "Destination branch '$new' already exists, skipping.\n";
            next;
        }

        # --- Handle Protected Branches ---
        # If the source branch is protected, we need to unprotect it first.
        if ($source_branch->{protected}) {
            $self->api->unprotect_branch($id, $old);
        }

        # --- Rename Logic ---
        eval {
# If the new branch name is a "subdirectory" of the old one (e.g. master -> master/foo),
# we need to use a temporary branch to avoid the new branch being deleted with the old one.
            if ($new =~ m/^$old\//) {
                my $temp_branch = "temp-rename-" . time();
                # 1. Create temporary branch from old branch.
                $self->api->create_branch($id,
                    { branch => $temp_branch, ref => $old });
                # 2. If old branch is default, set temporary branch as default.
                if ($old eq $default_branch) {
                    $self->api->edit_project($id,
                        { default_branch => $temp_branch });
                }
                # 3. Delete old branch.
                $self->api->delete_branch($id, $old);
                # 4. Create new branch from temporary branch.
                $self->api->create_branch($id,
                    { branch => $new, ref => $temp_branch });
                # 5. If temporary branch is default, set new branch as default.
                if ($temp_branch eq $self->api->project($id)->{default_branch})
                {
                    $self->api->edit_project($id, { default_branch => $new });
                }
                # 6. Delete temporary branch.
                $self->api->delete_branch($id, $temp_branch);
            } else {
                # For simple renames:
                # 1. Create new branch from old branch.
                $self->api->create_branch($id,
                    { branch => $new, ref => $old });
                # 2. If old branch is default, set new branch as default.
                if ($old eq $default_branch) {
                    $self->api->edit_project($id, { default_branch => $new });
                }
                # 3. Delete old branch.
                $self->api->delete_branch($id, $old);
            }
        };
        if ($@) {
            ds_warn "Branch rename has failed for $str\n";
            ds_verbose $@;
            if (!$self->config->no_fail) {
                ds_verbose "Use --no-fail to continue";
                return 1;
            }
            next;
        }

        # Store successfully renamed branch for later verification.
        $renamed_branches{$old} = $new;
    }

    # --- Verification ---
    if (not $no_wait and keys %renamed_branches) {
        ds_verbose
"Now polling Salsa to check if the changes are correctly reflected when listing branches...";

        my $all_verified = 0;
        my $retries      = 30;
        my $delay        = 3;

        for my $i (1 .. $retries) {
            my @remote_branches     = @{ $self->api->branches($id) };
            my %remote_branch_names = map { $_->{name} => 1 } @remote_branches;

            my $unverified_count = 0;
            for my $old (keys %renamed_branches) {
                my $new = $renamed_branches{$old};
                if (exists $remote_branch_names{$old}
                    or not exists $remote_branch_names{$new}) {
                    $unverified_count++;
                }
            }

            if ($unverified_count == 0) {
                $all_verified = 1;
                last;
            }

            if ($i == $retries) {
                ds_die "Failed to verify all branch renames.";
            }

            sleep $delay;
        }
        ds_verbose "All branch renames verified successfully."
          if $all_verified;
    }

    return 0;
}

1;
