package Bif::Sync::Server;
use strict;
use warnings;
use Bif::Mo;
use Bif::Sync::Plugin::Identity;
use Bif::Sync::Plugin::Project;
use Bif::Sync::Plugin::Repo;
use Coro::Handle;
use DBIx::ThinSQL qw/sq/;
use Log::Any '$log';

our $VERSION = '0.1.5_3';
extends 'Bif::Sync';

has hub_id => ( is => 'rw', );

# Names are reversed, so that the methods make sense from the server's
# point of view.

my %METHODS = (
    EXPORT => {
        project => 'import_project',
    },
    IMPORT => {
        hub     => 'export_hub',
        project => 'sync_project',
        self    => 'export_self',
    },
    SYNC => {
        hub      => 'sync_hub',
        projects => 'sync_projects',
    },
    TRANSFER => {
        hub_changes             => 'real_transfer_hub_changes',
        project_related_changes => 'real_transfer_project_related_changes',
    },
    QUIT => {},
);

sub BUILD {
    my $self = shift;
    $self->hub_id( $self->db->get_local_hub_id );
}

sub run {
    my $self = shift;
    $self->rh( Coro::Handle->new_from_fh( *STDIN, timeout => 30 ) );
    $self->wh( Coro::Handle->new_from_fh(*STDOUT) );
    $self->new_temp_table;

    while (1) {
        my ( $action, $type, @rest ) = $self->read;

        if ( $action eq 'EOF' ) {
            return;
        }
        elsif ( $action eq 'INVALID' ) {
            next;
        }
        elsif ( $action eq 'QUIT' ) {
            $self->write('Bye');
            return;
        }

        # TODO a VERSION check

        if ( !exists $METHODS{$action} ) {
            $self->write( 'InvalidAction', 'Invalid Action: ' . $action );
            next;
        }

        if ( !$type ) {
            $self->write( 'MissingType', 'missing [2] type' );
            next;
        }

        my $method = $METHODS{$action}->{$type};

        if ( !$self->can($method) ) {
            $self->write( 'TypeNotImplemented',
                'type not implemented: ' . $type );
            next;
        }

        my $response = eval {
            $self->db->txn(
                sub {
                    $self->$method(@rest);
                }
            );
        };

        if ($@) {
            $log->error($@);
            $self->write( 'InternalServerError', 'Internal Server Error: ',
                $action, $type, @rest );
            next;
        }

        if ( $response eq 'EOF' ) {
            return;
        }
        elsif ( $response eq 'INVALID' ) {
            next;
        }
        elsif ( $response eq 'QUIT' ) {
            $self->write('Bye');
            return;
        }
    }

    return;
}

sub export_self {
    my $self = shift;
    my $db   = $self->db;

    my ( $id, $uuid ) = $db->xlist(
        select     => [ 'bif.identity_id', 't.uuid' ],
        from       => 'bifkv bif',
        inner_join => 'topics t',
        on         => 't.id = bif.identity_id',
        where => { 'bif.key' => 'self' },
    );

    if ( !$uuid ) {
        $self->write( 'SelfNotFound', 'self identity not found here' );
        return 'SelfNotFound';
    }

    $self->write( 'EXPORT', 'identity', $uuid );
    return $self->real_export_identity($id);
}

sub export_hub {
    my $self = shift;
    my $name = shift;
    my $db   = $self->db;

    my ( $id, $uuid ) = $db->xlist(
        select     => [ 'h.id', 't.uuid' ],
        from       => 'projects p',
        inner_join => 'hubs h',
        on         => 'h.id = p.id',
        inner_join => 'topics t',
        on         => 't.id = h.id',
        where => { 'p.name' => $name },
    );

    if ( !$uuid ) {
        $self->write( 'HubNotFound', 'hub not found: ' . $name );
        return 'HubNotFound';
    }

    $self->write( 'EXPORT', 'hub', $uuid );
    return $self->real_export_hub($id);
}

sub sync_hub {
    my $self = shift;
    my $uuid = shift || 'unknown';
    my $hash = shift || 'unknown';

    my $db  = $self->db;
    my $hub = $db->xhashref(
        select     => [ 'h.id', 'h.hash' ],
        from       => 'topics t',
        inner_join => 'hubs h',
        on         => 'h.id = t.id',
        where => { 't.uuid' => $uuid },
    );

    if ( !$hub ) {
        $self->write( 'RepoNotFound', 'hub uuid not found here' );
        return 'RepoNotFound';
    }
    elsif ( $hub->{hash} eq $hash ) {
        $self->write( 'RepoMatch', 'no changes to exchange' );
        return 'RepoMatch';
    }

    $self->write( 'SYNC', 'hub', $uuid, $hub->{hash} );
    return $self->real_sync_hub( $hub->{id} );
}

sub import_project {
    my $self = shift;
    my $uuid = shift;
    my $path = shift;

    if ( !$uuid ) {
        $self->write( 'MissingUUID', 'uuid is required' );
        return;
    }
    elsif ( !$path ) {
        $self->write( 'MissingPath', 'path is required' );
        return;
    }

    my $local = $self->db->xhashref(
        select    => [ 'p.id AS id', 't2.uuid AS other_uuid', ],
        from      => '(select 1,2)',
        left_join => 'topics t',
        on        => { 't.uuid'  => $uuid },
        left_join => 'projects p',
        on        => 'p.id = t.id',
        left_join => 'projects AS p2',
        on        => { 'p2.path' => $path },
        left_join => 'topics AS t2',
        on        => 't2.id = p2.id',
        limit     => 1,
    );

    if ( $local->{id} ) {
        $self->write( 'ProjectFound', 'project exists' );
        return 'ProjectFound';
    }
    elsif ( $local->{other_uuid} ) {
        $self->write( 'PathExists', 'path is ' . $local->{other_uuid} );
        return 'PathExists';
    }

    $self->write( 'IMPORT', 'project', $uuid );
    my $status = $self->real_import_project($uuid);

    $self->db->xdo(
        update => 'projects',
        set    => 'local = 1',
        where  => {
            id => sq(
                select => 't.id',
                from   => 'topics t',
                where  => { 't.uuid' => $uuid, },
            ),
        },
    );

    return $status;
}

sub sync_projects {
    my $self = shift;
    my @ids;

    foreach my $pair (@_) {
        my ( $uuid, $hash ) = @$pair;

        if ( !$uuid ) {
            $self->write( 'MissingUUID', 'uuid is required' );
            return 'MissingUUID';
        }
        elsif ( !defined $hash ) {
            $self->write( 'MissingHash', 'hash is required' );
            return 'MissingHash';
        }

        my $pinfo = $self->db->xhashref(
            select     => [ 't.id', 'hrp.hash' ],
            from       => 'topics t',
            inner_join => 'hub_related_projects hrp',
            on         => {
                'hrp.project_id' => \'t.id',
                'hrp.hub_id'     => $self->hub_id,
            },
            where => { 't.uuid' => $uuid },
        );

        if ( !$pinfo ) {
            $self->write( 'ProjectNotFound', 'project not found: ' . $uuid );
            return 'ProjectNotFound';
        }

        push( @ids, $pinfo->{id} );

        #        if ( $pinfo->{hash} eq $hash ) {
        #            $self->write( 'ProjectMatch', $uuid, $pinfo->{hash} );
        #            return 'ProjectMatch';
        #        }
    }

    $self->write( 'SYNC', 'projects' );

    foreach my $id (@ids) {
        my $status = $self->real_sync_project( $id, \@ids );
        return $status unless $status eq 'ProjectSync';
    }

    return 'ProjectSync';
}

sub disconnect {
    my $self = shift;
    $log->info('disconnect');
    $self->rh->close;
    $self->wh->close;
    return;
}

1;

=head1 NAME

=for bif-doc #perl

Bif::Sync::Server - server for communication with a client

