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_5';
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',
        project => 'sync_project',
    },
    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', 'n.uuid' ],
        from       => 'bifkv bif',
        inner_join => 'nodes n',
        on         => 'n.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 $db   = $self->db;

    my ( $id, $uuid ) = $db->xlist(
        select     => [ 'h.id', 'n.uuid' ],
        from       => 'bifkv b',
        inner_join => 'hubs h',
        on         => 'h.id = b.hub_id',
        inner_join => 'nodes n',
        on         => 'n.id = h.id',
        where => { 'b.key' => 'local' },
    );

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

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

sub sync_hub {
    my $self = shift;
    my $uuid = shift;
    my $hash = shift;
    my $db   = $self->db;

    unless ( defined $uuid and defined $hash ) {
        $self->write( 'ProtocolError', 'sync_hub($uuid,$hash)' );
        return 'ProtocolError';
    }

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

    if ( !$hub ) {
        $self->write( 'RepoNotFound', 'hub not found here' );
        return 'RepoNotFound';
    }

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

    return 'HubMatch' if $hub->{hash} eq $hash;
    return $self->real_sync( 'hub', $hub->{id} );
}

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

    $self->write( 'ProtocolError', 'uuid/path is required' )
      unless $uuid and $path;

    my $local = $self->db->xhashref(
        select    => [ 'p.id AS id', 'n2.uuid AS other_uuid', ],
        from      => '(select 1,2)',
        left_join => 'nodes n',
        on        => { 'n.uuid'  => $uuid },
        left_join => 'nodes n2',
        on        => { 'n2.path' => $path },
        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 => 'n.id',
                from   => 'nodes n',
                where  => { 'n.uuid' => $uuid, },
            ),
        },
    );

    return $status;
}

sub sync_project {
    my $self = shift;
    my $uuid = shift;
    my $hash = shift;
    my $db   = $self->db;

    unless ( defined $uuid and defined $hash ) {
        $self->write( 'ProtocolError', 'sync_project($uuid,$hash)' );
        return 'ProtocolError';
    }

    my $pinfo = $self->db->xhashref(
        select     => [ 'n.id', 'p.hash' ],
        from       => 'nodes n',
        inner_join => 'projects p',
        on         => 'p.id = n.id',
        where => { 'n.uuid' => $uuid },
    );

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

    $self->write( 'SYNC', 'project', $uuid, $pinfo->{hash} );

    return 'ProjectMatch' if $pinfo->{hash} eq $hash;
    return $self->real_sync( 'project', $pinfo->{id} );
}

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

