#!/usr/bin/env perl
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

# Migrate old custom field based tracking flags to the new
# table based tracking flags

use strict;
use warnings;
use 5.10.1;

use lib qw(. lib local/lib/perl5);

BEGIN {
  use Bugzilla;
  Bugzilla->extensions;
}

use Bugzilla::Constants;
use Bugzilla::Field;
use Bugzilla::Product;
use Bugzilla::Component;
use Bugzilla::Extension::BMO::Data;
use Bugzilla::Install::Util qw(indicate_progress);

use Bugzilla::Extension::TrackingFlags::Constants;
use Bugzilla::Extension::TrackingFlags::Flag;
use Bugzilla::Extension::TrackingFlags::Flag::Bug;
use Bugzilla::Extension::TrackingFlags::Flag::Value;
use Bugzilla::Extension::TrackingFlags::Flag::Visibility;

use Getopt::Long;
use Data::Dumper;

Bugzilla->usage_mode(USAGE_MODE_CMDLINE);

my ($dry_run, $trace) = (0, 0);
GetOptions("dry-run" => \$dry_run, "trace" => \$trace,) or exit;

my $dbh = Bugzilla->dbh;

$dbh->{TraceLevel} = 1 if $trace;

my %product_cache;
my %component_cache;

sub migrate_flag_visibility {
  my ($new_flag, $products) = @_;

  # Create product/component visibility
  foreach my $prod_name (keys %$products) {
    $product_cache{$prod_name} ||= Bugzilla::Product->new({name => $prod_name});
    if (!$product_cache{$prod_name}) {
      warn "No such product $prod_name\n";
      next;
    }

    # If no components specified then we do Product/__any__
    # otherwise, we enter an entry for each Product/Component
    my $components = $products->{$prod_name};
    if (!@$components) {
      Bugzilla::Extension::TrackingFlags::Flag::Visibility->create({
        tracking_flag_id => $new_flag->flag_id,
        product_id       => $product_cache{$prod_name}->id,
        component_id     => undef
      });
    }
    else {
      foreach my $comp_name (@$components) {
        my $comp_matches = [];

        # If the component is a regexp, we need to find all components
        # matching the regex and insert each individually
        if (ref $comp_name eq 'Regexp') {
          my $comp_re = $comp_name;
          $comp_re =~ s/\?\-xism://;
          $comp_re =~ s/\(//;
          $comp_re =~ s/\)//;
          $comp_matches = $dbh->selectcol_arrayref(
            'SELECT components.name FROM components
                          WHERE components.product_id = ?
                                AND '
              . $dbh->sql_regexp('components.name', $dbh->quote($comp_re)) . '
                          ORDER BY components.name', undef, $product_cache{$prod_name}->id
          );
        }
        else {
          $comp_matches = [$comp_name];
        }

        foreach my $comp_match (@$comp_matches) {
          $component_cache{"${prod_name}:${comp_match}"}
            ||= Bugzilla::Component->new({
            name => $comp_match, product => $product_cache{$prod_name}
            });
          if (!$component_cache{"${prod_name}:${comp_match}"}) {
            warn "No such product $prod_name and component $comp_match\n";
            next;
          }

          Bugzilla::Extension::TrackingFlags::Flag::Visibility->create({
            tracking_flag_id => $new_flag->flag_id,
            product_id       => $product_cache{$prod_name}->id,
            component_id     => $component_cache{"${prod_name}:${comp_match}"}->id,
          });
        }
      }
    }
  }
}

sub migrate_flag_values {
  my ($new_flag, $field) = @_;

  print "Migrating flag values...";

  my %blocking_trusted_requesters
    = %{$Bugzilla::Extension::BMO::Data::blocking_trusted_requesters};
  my %blocking_trusted_setters
    = %{$Bugzilla::Extension::BMO::Data::blocking_trusted_setters};
  my %status_trusted_wanters
    = %{$Bugzilla::Extension::BMO::Data::status_trusted_wanters};
  my %status_trusted_setters
    = %{$Bugzilla::Extension::BMO::Data::status_trusted_setters};

  my %group_cache;
  foreach my $value (@{$field->legal_values}) {
    my $group_name = 'everyone';

    if ($field->name =~ /^cf_(blocking|tracking)_/) {
      if ($value->name ne '---' && $value->name !~ '\?$') {
        $group_name = get_setter_group($field->name, \%blocking_trusted_setters);
      }
      if ($value->name eq '?') {
        $group_name = get_setter_group($field->name, \%blocking_trusted_requesters);
      }
    }
    elsif ($field->name =~ /^cf_status_/) {
      if ($value->name eq 'wanted') {
        $group_name = get_setter_group($field->name, \%status_trusted_wanters);
      }
      elsif ($value->name ne '---' && $value->name ne '?') {
        $group_name = get_setter_group($field->name, \%status_trusted_setters);
      }
    }

    $group_cache{$group_name} ||= Bugzilla::Group->new({name => $group_name});
    $group_cache{$group_name} || die "Setter group '$group_name' does not exist";

    Bugzilla::Extension::TrackingFlags::Flag::Value->create({
      tracking_flag_id => $new_flag->flag_id,
      value            => $value->name,
      setter_group_id  => $group_cache{$group_name}->id,
      sortkey          => $value->sortkey,
      is_active        => $value->is_active
    });
  }

  print "done.\n";
}

sub get_setter_group {
  my ($field, $trusted) = @_;
  my $setter_group = $trusted->{'_default'} || "";
  foreach my $dfield (keys %$trusted) {
    if ($field =~ $dfield) {
      $setter_group = $trusted->{$dfield};
    }
  }
  return $setter_group;
}

sub migrate_flag_bugs {
  my ($new_flag, $field) = @_;

  print "Migrating bug values...";

  my $bugs = $dbh->selectall_arrayref(
    "SELECT bug_id, " . $field->name . "
                                           FROM bugs
                                          WHERE " . $field->name . " != '---'
                                       ORDER BY bug_id"
  );
  local $| = 1;
  my $count = 1;
  my $total = scalar @$bugs;
  foreach my $row (@$bugs) {
    my ($id, $value) = @$row;
    indicate_progress({current => $count++, total => $total, every => 25});
    Bugzilla::Extension::TrackingFlags::Flag::Bug->create({
      tracking_flag_id => $new_flag->flag_id,
      bug_id           => $id,
      value            => $value,

    });
  }

  print "done.\n";
}

sub migrate_flag_activity {
  my ($new_flag, $field) = @_;

  print "Migrating flag activity...";

  my $new_field = Bugzilla::Field->new({name => $new_flag->name});
  $dbh->do("UPDATE bugs_activity SET fieldid = ? WHERE fieldid = ?",
    undef, $new_field->id, $field->id);

  print "done.\n";
}

sub do_migration {
  my $bmo_tracking_flags
    = $Bugzilla::Extension::BMO::Data::cf_visible_in_products;
  my $bmo_project_flags  = $Bugzilla::Extension::BMO::Data::cf_project_flags;
  my $bmo_disabled_flags = $Bugzilla::Extension::BMO::Data::cf_disabled_flags;

  my $fields
    = Bugzilla::Field->match({custom => 1, type => FIELD_TYPE_SINGLE_SELECT});

  my @drop_columns;
  foreach my $field (@$fields) {
    next if $field->name !~ /^cf_(blocking|tracking|status)_/;

    foreach my $field_re (keys %$bmo_tracking_flags) {
      next if $field->name !~ $field_re;

      # Create the new tracking flag if not exists
      my $new_flag
        = Bugzilla::Extension::TrackingFlags::Flag->new({name => $field->name});

      next if $new_flag;

      print "----------------------------------\n"
        . "Migrating custom tracking field "
        . $field->name . "...\n";

      my $new_flag_name = $field->name . "_new"; # Temporary name til we delete the old

      my $type
        = grep($field->name =~ $_, @$bmo_project_flags) ? 'project' : 'tracking';

      my $is_active = grep($_ eq $field->name, @$bmo_disabled_flags) ? 0 : 1;

      $new_flag = Bugzilla::Extension::TrackingFlags::Flag->create({
        name        => $new_flag_name,
        description => $field->description,
        type        => $type,
        sortkey     => $field->sortkey,
        is_active   => $is_active,
        enter_bug   => $field->enter_bug,
      });

      migrate_flag_visibility($new_flag, $bmo_tracking_flags->{$field_re});

      migrate_flag_values($new_flag, $field);

      migrate_flag_bugs($new_flag, $field);

      migrate_flag_activity($new_flag, $field);

      push(@drop_columns, $field->name);

      # Remove the old flag entry from fielddefs
      $dbh->do("DELETE FROM fielddefs WHERE name = ?", undef, $field->name);

      # Rename the new flag
      $dbh->do("UPDATE fielddefs SET name = ? WHERE name = ?",
        undef, $field->name, $new_flag_name);

      $new_flag->set_name($field->name);
      $new_flag->update;

      # more than one regex could possibly match but we only want the first one
      last;
    }
  }

  # Drop each custom flag's value table and the column from the bz schema object
  if (!$dry_run && @drop_columns) {
    print "Dropping value tables and updating bz schema object...\n";

    foreach my $column (@drop_columns) {

      # Drop the values table
      $dbh->bz_drop_table($column);

      # Drop the bugs table column from the bz schema object
      $dbh->_bz_real_schema->delete_column('bugs', $column);
      $dbh->_bz_store_real_schema;
    }

    # Do the one alter table to drop all columns at once
    $dbh->do(
      "ALTER TABLE bugs DROP COLUMN " . join(", DROP COLUMN ", @drop_columns));
  }
}

# Start Main

eval {
  if ($dry_run) {
    print "** dry run : no changes to the database will be made **\n";
    $dbh->bz_start_transaction();
  }
  print "Starting migration...\n";
  do_migration();
  $dbh->bz_rollback_transaction() if $dry_run;
  print "All done!\n";
};
if ($@) {
  $dbh->bz_rollback_transaction() if $dry_run;
  die "$@" if $@;
}
