package Common::Deploy;

use strict;

use Data::Dumper;

use File::Basename;

use lib '/app/tools/common/lib';
use Common::Config;
use Common::Deploy::Info;
use Common::Deploy::SwitchBranch;

use base 'Common::XMLObject';

sub _modules {
    qw( admin appuser bookpub chart_director common corp data_classes dbadmin
      distribution job mediaserve metadata raptor rdas rps sale_import stats
      support sysadmin cpan report );
}

sub _validActions { qw( status list list_branches list_releases ) }

sub _init {
    my $self = shift;
    my %args = @_;

    $self->SUPER::_init(@_);

    foreach my $key ( keys(%args) ) {
        $self->{"_$key"} = $args{$key};
    }

    return $self;
}

sub run {
    my $self = shift;

    my @validActions = $self->_validActions();
    my $found;

    foreach my $action (@validActions) {
        $found = 1 if ( defined( $self->{"_$action"} ) );
    }

    $found = 1 if ( $self->{_force} );

    return "ERROR: Action required.\n" . "Valid actions for $self->{_process_type}: " . join( ", ", @validActions )
      unless ($found);

    if ( $self->{_create_branch} ) {
        $self->_createBranch( $self->{_create_branch} );
    } elsif ( defined( $self->{_remove} ) ) {
        $self->_remove( $self->{_remove} );
    } elsif ( defined( $self->{_create_release} ) ) {
        $self->_createRelease( $self->{_create_release} );
    } elsif ( defined( $self->{_list_branches} ) ) {
        $self->_listBranches();
    } elsif ( defined( $self->{_list_releases} ) ) {
        $self->_listReleases();
    } elsif ( defined( $self->{_status} ) ) {
        $self->_status( length( $self->{_status} ) ? $self->{_status} : undef );
    } elsif ( defined( $self->{_checkout} ) ) {
        $self->_checkout( $self->{_checkout} );
    } elsif ( defined( $self->{_push} ) ) {
        $self->_push();
    } elsif ( defined( $self->{_merge} ) ) {
        $self->_merge( $self->{_merge} );
    } else {
        die "Unhandled command";
    }

    return;
}

sub _getConfigOption {
    my $self = shift;
    my $option = shift || die;

    my $config = new Common::Config();
    return $config->get($option);
}

sub repository {
    my $self = shift;

    if ( Common::RSApp::IsStagingServer() ) {
        return $self->_getConfigOption('staging_repository');
    } elsif ( !Common::RSApp::IsProductionServer() ) {
        return $self->_getConfigOption('development_repository');
    } else {
        die "Error: Not allowed on production servers";
    }
}

sub mysqlRepository {
    my $self = shift;

    if ( Common::RSApp::IsStagingServer() ) {
        return $self->_getConfigOption('staging_mysql_repository');
    } elsif ( !Common::RSApp::IsProductionServer() ) {
        return $self->_getConfigOption('development_mysql_repository');
    } else {
        die "Error: Not allowed on production servers";
    }
}

sub mysqlDir {
    my $self = shift;

    if ( Common::RSApp::IsStagingServer() ) {
        return $self->_getConfigOption('staging_mysql_dir');
    } elsif ( !Common::RSApp::IsProductionServer() ) {
        return $self->_getConfigOption('development_mysql_dir');
    } else {
        die "Error: Not allowed on production servers";
    }
}

sub codeDir {
    my $self = shift;

    if ( Common::RSApp::IsStagingServer() ) {
        return $self->_getConfigOption('staging_code_dir');
    } elsif ( !Common::RSApp::IsProductionServer() ) {
        return $self->_getConfigOption('development_code_dir');
    } else {
        die "Error: Not allowed on production servers";
    }
}

sub validateDirectoryStructure {
    my $self = shift;
    my @errors;
    my $repository      = $self->repository();
    my $mysqlRepository = $self->mysqlRepository();
    my $mysqlDir        = $self->mysqlDir();
    my $codeDir         = $self->codeDir();

    push( @errors, "Code repository doesn't exist: '$repository'" ) unless ( -e $repository );
    push( @errors, "Code repository is not a diretory: '$repository'" ) if ( -e $repository && !-d $repository );

    push( @errors, "Code dir is not a symbolic link: '$codeDir'" ) if ( -e $codeDir && !-l $codeDir );

    push( @errors, "MySQL repository doesn't exist: '$mysqlRepository'" ) unless ( -e $mysqlRepository );
    push( @errors, "Code repository is not a diretory: '$mysqlRepository'" ) if ( -e $mysqlRepository && !-d $mysqlRepository );

    push( @errors, "MySQL dir is not a symbolic link: '$mysqlDir'" ) if ( -e $mysqlDir && !-l $mysqlDir );

    if (@errors) {
        die "\nERROR: Directory validation failed with the following errors: \n" . join( "\n", @errors ) . "\n\n";
    }
}

sub getCurrentDatabaseBranch {
    my $self            = shift;
    my $mysqlDir        = $self->mysqlDir();
    my $mysqlRepository = $self->mysqlRepository();
    my $dirname         = dirname($mysqlDir);
    my $basename        = basename($mysqlDir);

    my $cmd = "find $dirname -maxdepth 1 -type l -name $basename -ls";
    Common::Log::Debug("EXECUTE: $cmd");

    open( IN, "$cmd | " ) || die("Failed: $!");

    my $file = <IN>;
    chomp($file);

    return unless ($file);

    if ( $file =~ /.*$mysqlRepository\/(.*)$/ ) {
        close(IN);

        Common::Log::Debug("Current Database Branch: $1");
        return $1;
    }

    $file =~ s/.* -> //g;

    die "$mysqlDir is linked to '$file' which doens't appear to be a valid branch";
}

sub getCurrentBranch {
    my $self       = shift;
    my $codeDir    = $self->codeDir();
    my $repository = $self->repository();
    my $dirname    = dirname($codeDir);
    my $basename   = basename($codeDir);

    my $cmd = "find $dirname -maxdepth 1 -type l -name $basename -ls";
    Common::Log::Debug("EXECUTE: $cmd");

    open( IN, "$cmd | " ) || die("Failed: $!");

    my $file = <IN>;
    chomp($file);

    return unless ($file);

    if ( $file =~ /.*$repository\/(.*)\/code/ ) {
        close(IN);

        Common::Log::Debug("Current Branch: $1");
        return $1;
    }

    $file =~ s/.* -> //g;

    die "$codeDir is linked to '$file' which doens't appear to be a valid branch";
}

sub getLocalBranches {
    my $self = shift;

    my $repository = $self->repository();

    opendir( my $dh, $repository ) || die "Can not read from repository dir '$repository': $!";
    my @branches = grep { /^[^\.]/ && -d "$repository/$_" && -d "$repository/$_/code" } readdir($dh);

    closedir($dh);
    return @branches;
}

sub _status {
    my $self       = shift;
    my $repository = $self->repository();
    my $branch     = shift || $self->getCurrentBranch();

    Common::Log::Debug("Getting status of the current branch");

    die "$repository/$branch doesn't appear to be a valid branch"
      unless ( -d "$repository/$branch/code" );

    my $status = $self->_getCVSStatus("$repository/$branch/code");

    if ($status) {
        print "status for branch '$branch'\n";
        foreach my $s ( keys(%$status) ) {
            foreach my $file ( @{ $status->{$s} } ) {
                print "$file\t$s\n";
            }
        }
    } else {
        print "branch '$branch' is up to date\n";
    }

    return;
}

sub _getCVSStatus {
    my $self = shift;
    my $path = shift || die "path required";
    my $host = shift;
    my $result;
    my $codeDir = basename( $self->codeDir() );

    my $cmd = "(cd $path && cvs -q status 2>&1)";
    $cmd = "ssh $host $cmd" if ($host);

    Common::Log::Debug("Execute: $cmd");

    my $output = `$cmd`;
    die "CVS status failed\noutput:\n$output" if ($?);

    my $status;

    foreach my $line ( split( /\n/, $output ) ) {
        if ( $line =~ /\tStatus: (.*)/ ) {
            $status = $1;
        }

        if ( $line =~ /\/cvsroot\/$codeDir\/(.*),/ && $status ne 'Up-to-date' ) {
            $result->{$status} = [] unless ( $result->{$status} );
            push( @{ $result->{$status} }, $1 );
        }
    }

    return $result;
}

sub _createBranch {
    my $self       = shift;
    my $branchName = shift || die;
    my $path       = $self->repository() . "/$branchName";

    die "Path already exists. Won't overwrite: '$path'\n" if ( -d $path );

    Common::Deploy::Info->addBranch($branchName);

    $self->_createFromName($branchName);

    my $switch = new Common::Deploy::SwitchBranch();
    $switch->switchBranch($branchName);

    return;
}

sub _createRelease {
    my $self        = shift;
    my $releaseName = shift;

    unless ( $releaseName && length($releaseName) ) {
        my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time);
        $releaseName = sprintf( "RELEASE_%04d%02d%02d_%02d%02d%02d", $year, $mon, $mday, $hour, $min, $sec );
    }

    my $path = $self->repository() . "/$releaseName";
    die "Path already exists. Won't overwrite: '$path'\n" if ( -d $path );

    Common::Deploy::Info->addRelease($releaseName);

    $self->_createFromName($releaseName);

    return;
}

sub _createFromName {
    my $self = shift;
    my $name = shift || die;
    my $path = $self->repository() . "/$name/code";

    die "Name can not contain spaces" if ( $name =~ /\s/ );
    die "Path already exists. Won't overwrite: '$path'" if ( -d $path );

    my $output = `mkdir -p "$path" 2>&1`;
    die "$output" if ($?);

    my $cmd = "cd $path && cvs ";

    foreach my $module ( $self->_modules ) {
        print " - Checking out module '$module'\n";
        Common::Log::Debug("Executing: $cmd -q co -P -A $module");
        $output = `$cmd -q co -P -A $module 2>&1`;
        die "$output" if ($?);
    }

    print "Tagging branch with '$name'\n";
    Common::Log::Debug("Executing: $cmd -q tag -b $name");
    $output = `$cmd -q tag -b $name 2>&1`;

    print "Updating to branch '$name'\n";
    Common::Log::Debug("Executing: $cmd -q up -r $name");
    $output = `$cmd -q up -r $name 2>&1`;

}

sub checkoutBranch {
    my $self   = shift;
    my $branch = shift || die "Branch name required";
    my $path   = shift;

    die "Unknown branch '$branch'" unless ( $self->_isValidBranch($branch) );

}

sub _remove {
    my $self = shift;
    my $branch = shift || die "Branch name required";

    my $path = $self->repository() . "/$branch/code";

    die "'$branch' is not checked out on this server\n" unless ( -d $path );

    print "Removing local branch '$branch'\n";

    print " - checking branch status\n";
    my $status = $self->_getCVSStatus("$path");

    if ( $status && !$self->{_force} ) {
        die "$branch is not Up-to-date.  Correct this or us --force.\n";
    }

    $path = dirname($path);
    print " - removing '$path'\n";
    my $output = `rm -r $path`;

    die $output if ($?);
}

sub _checkout {
    my $self = shift;
    my $branch = shift || die "Branch name required";

    my $path = $self->repository() . "/$branch";

    die "'$branch' is already checked out on this server\n" if ( -d $path );
    die "'$branch' is not a valid branch\n" unless ( Common::Deploy::Info->branchExist($branch) );

    print "Checking out branch '$branch'\n";

    $path .= "/code";
    my $output = `mkdir -p $path 2>&1`;
    die $output if ($?);

    foreach my $module ( $self->_modules ) {
        print " - checking out '$module'\n";

        my $cmd = "cd $path && cvs -q co -r $branch $module";
        Common::Log::Debug("Execute: $cmd");

        my $output = `$cmd 2>&1`;
        die $output if ($?);
    }
}

sub _isValidBranch {
    my $self = shift;
    my $branch = shift || return undef;

    return 1 if ( $branch eq 'trunk' );

    return 1 if ( $self->validName($branch) );

    return;
}

sub _listBranches {
    my $self     = shift;
    my @branches = Common::Deploy::Info->branches();

    foreach my $release (@branches) {
        print "$release->{name}\t($release->{created})\n";
    }
}

sub _listReleases {
    my $self     = shift;
    my @releases = Common::Deploy::Info->releases();

    foreach my $release (@releases) {
        print "$release->{name}\t( created: $release->{created} released: $release->{released} )\n";
    }
}

sub list {
    my $self = shift;

    my @branches = Common::Deploy::Info->branches();
    my @releases = Common::Deploy::Info->releases();

    my $count = 5;

    print "Recent branches:\n";
    for ( my $i = @branches - 1 ; $count > 0 && $i >= 0 ; $i-- ) {
        print "$branches[$i]->{name}\t($branches[$i]->{created})\n";
        $count--;
    }

    $count = 5;
    print "\nRecent releases:\n";
    for ( my $i = @releases - 1 ; $count > 0 && $i >= 0 ; $i-- ) {
        print "$releases[$i]->{name}\t( created: $releases[$i]->{created} released: $releases[$i]->{released} )\n";
        $count--;
    }
}

sub _push { die "overload this method" }

sub _promptYesNo {
    my $self = shift;
    my $prompt = shift || die "Prompt required";

    print "$prompt ";

    my $yesno = <STDIN>;
    chomp $yesno;

    return $yesno && $yesno =~ /^yes$/i;
}

sub _merge {
    my $self    = shift;
    my $branch  = $self->_getMergeBranch(shift);
    my $ignore  = $self->_getIgnoredFiles();
    my $include = $self->_getIncludedFiles();

    $self->_mergeBranch( source => 'trunk', destination => $branch, ignore => $ignore, include => $include );

}

sub _getMergeBranch {
    my $self   = shift;
    my $branch = shift;

    unless ($branch) {
        $branch = $self->getCurrentBranch();
        $self->_promptYesNo("No branch name specified, would you like to merge into the current branch '$branch'? (yes/no) ")
          || die "aborted\n";
    }

    return $branch;
}

sub _getIgnoredFiles {
    my $self        = shift;
    my $include     = $self->{_ignore};
    my $includeFile = $self->{_ignore_file};

    return $self->_getFiles( include => $include, includeFile => $includeFile );
}

sub _getIncludedFiles {
    my $self        = shift;
    my $include     = $self->{_include};
    my $includeFile = $self->{_include_file};

    return $self->_getFiles( include => $include, includeFile => $includeFile );
}

sub _getFiles {
    my $self = shift;
    my %args = @_;

    return unless ( $args{include} || $args{includeFile} );

    my @files;

    if ( $args{include} ) {
        @files = ref( $args{include} ) eq 'ARRAY' ? @{ $args{include} } : split( /,/, $args{include} );
    }

    if ( $args{includeFile} ) {
        open( INFILE, $args{includeFile} ) || die "Failed to open input file '$args{includeFile}': $!\n";
        while (<INFILE>) {
            chomp;
            push( @files, $_ );
        }
    }

    return \@files;
}

sub _mergeBranch {
    my $self         = shift;
    my %args         = @_;
    my $src          = $args{source};
    my $dest         = $args{destination};
    my $ignoreFiles  = $args{ignore};
    my $includeFiles = $args{include};

    die "Source '$src' and Destination '$dest' can not be the same\n" if ( $src eq $dest );

    $src = 'HEAD' if ( $src eq 'trunk' );

    print "Merging '$src' into '$dest'\n";
    Common::Log::Debug( "Ignore: \n  " . join( "\n  ", @$ignoreFiles ) ) if ($ignoreFiles);
    Common::Log::Debug( "Include: \n  " . join( "\n  ", @$includeFiles ) ) if ($includeFiles);

    if ( $ignoreFiles && @$ignoreFiles ) {
        foreach (@$ignoreFiles) {
            die "Ignore file '$_' can not be an absolute path\n" if (/^\//);
        }
    }

    if ( $includeFiles && @$includeFiles ) {
        foreach (@$includeFiles) {
            die "Include file '$_' can not be an absolute path\n" if (/^\//);
        }
    }

    my $localRepository = $self->repository() . "/$dest/code";

    my $cmd = "cd $localRepository && cvs -q up -j $src 2>&1";
    Common::Log::Debug("Execute: $cmd");
    my $output = `$cmd`;

    die "Command Failed\n" . "Command: $cmd\n" . "Status: $?\nOutput: $output" if ( $? && !grep( /^C /, split( /\n/, $output ) ) );

    print $output;

}

1;
