package Common::DB::Deploy;

use strict;
use warnings;

use Common::DB::Deploy::CommandOption;
use Common::DB::Deploy::ClientManager;
use Common::DB::Deploy::RepositoryManager;
use Common::DB::Deploy::DatabaseManager;
use Common::DB::Deploy::VersionManager;
use Common::DB::Deploy::ScriptsApplier;

sub new {
    my ($class, %args) = @_;

    my $self = bless {
        oCommandOption            => undef,
        oClientManager            => undef,
        oRepositoryManager        => undef,
        available_local_databases => undef,
        excluded_hosts            => [],
        excluded_databases        => [],
        %args,
    }, $class;

    $self->_init();
    return $self;
}

sub config {
    return shift->{oConfig};
}

sub logger {
    return shift->{oLogger};
}

sub clientManager {
    return shift->{oClientManager};
}

sub repositoryManager {
    return shift->{oRepositoryManager};
}

sub commandOption {
    return shift->{oCommandOption};
}

sub _init {
    my $self = shift;

    $self->{oCommandOption} = Common::DB::Deploy::CommandOption->new();

    # prepare a list of available databases based on Common::RSDB::CLIENT_DB
    $self->{oClientManager} = Common::DB::Deploy::ClientManager->new(
        oLogger        => $self->logger,
        client_db_list => $self->{client_db_list}
    );

    # initialize local directories with sql patches
    $self->{oRepositoryManager} = Common::DB::Deploy::RepositoryManager->new(
        oLogger            => $self->logger,
        repository_dirs    => $self->{repository_dirs},
        allowed_dir_prefix => $self->{allowed_dir_prefix}
    );

    # get a list of the local databases, if not production mode.
    unless ( $self->commandOption->prodmode ) {
        $self->logger->info("The script is running in non-production mode.");
        $self->{available_local_databases} = $self->_getAvailableLocalDatabases();
    }

    # prepare the lists of excluded hosts and databases
    $self->_prepareExcludedHostsDatabases();

    return 1;
}

sub process {
    my $self = shift;

    foreach my $hDSN ( @{ $self->clientManager->getListOfDSN() } ) {
        my $database = $hDSN->{database};

        # next if not production mode OR local db doesn't exist (non-prodmode)
        next if !$self->commandOption->prodmode && !exists $self->{available_local_databases}{$database};

        if ( $self->_isExcludedNode($hDSN) ) {
            $self->logger->info("Skipping the excluded database '$hDSN->{database}' on the host $hDSN->{host}.");
            next;
        }

        my $oDB = Common::DB::Deploy::DatabaseManager->new(
            oLogger => $self->logger,
            %$hDSN,
        );

        my $oVersionManager = Common::DB::Deploy::VersionManager->new(
            oLogger            => $self->logger,
            oCommandOption     => $self->commandOption,
            oRepositoryManager => $self->repositoryManager,
            oDatabaseManager   => $oDB,
        );

        my $oScriptsApplier = Common::DB::Deploy::ScriptsApplier->new(
            oLogger          => $self->logger,
            oCommandOption   => $self->commandOption,
            oVersionManager  => $oVersionManager,
        )->run();

        $oDB->disconnect;
    }

    return 1;
}

sub _getAvailableLocalDatabases {
    my $self = shift;

    my $oDB;

    eval {
        # try to connect to the local host first
        $self->logger->info("Checking MySQL database on localhost.");
        $oDB = Common::DB::Deploy::DatabaseManager->new(
            oLogger         => $self->logger,
            connect_retries => 1,
            raise_error     => 0,
        );
    };

    if ($@) {
        # second attempt with data from config
        my $predifinedHost = $self->config->get('dev_db_host');
        $self->logger->info("Checking MySQL database on the predefined host: $predifinedHost.");
        $oDB = Common::DB::Deploy::DatabaseManager->new(
            oLogger  => $self->logger,
            host     => $predifinedHost,
            port     => $self->config->get('dev_db_port'),
            user     => $self->config->get('dev_db_user'),
            pass     => $self->config->get('dev_db_pass'),
        );
    }

    my %availableLocalDBs = $oDB->getLocalDatabases();

    return \%availableLocalDBs;
}

sub _isExcludedNode {
    my ( $self, $hDSN ) = @_;

    return 1 if grep { $_ =~ /^$hDSN->{host}$/i } @{ $self->{excluded_hosts} };
    return 1 if grep { $_ =~ /^$hDSN->{database}$/i } @{ $self->{excluded_databases} };

    return 0;
}

sub _prepareExcludedHostsDatabases {
    my $self = shift;

    foreach my $item ( qw/excluded_hosts excluded_databases/ ) {
        @{ $self->{$item} } = grep { $_ } split( /,|;|\s/, $self->config->get("db_deploy_$item") );
    }

    return 1;
}


1;