package CF2022::Controller::Orders;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::UserAgent;
use Mojo::Asset::File;
use Image::PNG::QRCode 'qrpng';

use constant TPL_VS      => '%d%07d';
use constant TPL_PAYMENT => 'SPD*1.0*ACC:%s*AM:%.2f*MSG:%s, %s*X-ID:%s*X-VS:%d*X-SS:%d*X-KS:%d';

use feature 'signatures';
no warnings qw{ experimental::signatures };

sub create ($c ) {
    $c->openapi->valid_input or return;

    my $args      = $c->req->json;
    my $claims;
    my $group;
    my @products      = ();
    my $accredited    = 0;
    my $accreditation = 0;

    # clasifikator typu
    my $pt = {};
    CLASS:
    foreach my $class ( qw(main nonfree subsidy) ) {
        foreach my $id ( split /\D+/, $c->config->{"products_$class"} ) {
            $pt->{$class}{$id} = 1;
        }
    }

    my $url = sprintf ('%s/organizers/%s/events/%s/orders/',
        $c->config->{pretix_api},
        $c->config->{pretix_organizer},
        $c->config->{pretix_event},
    );

    my @answers = map {{
        question => $_->{question_id},
        answer   => $_->{response},
    }} @{ $args->{responses} };

    # processing tokenu

    if ( $args->{token} ) {
        $c->oauth_token($args->{token});

        $claims = $c->oauth_claims;
        return $c->error(403, 'Invalid token') if ! $claims;

        $group = $c->oauth_main_group;
        return $c->error(403, 'Invalid token') if ! $group;

        $args->{name} = $claims->{name};

        push @answers, (
            {
                question => $c->config->{pretix_qid_sso},
                answer   => $claims->{sub},
            },
            {
                question => $c->config->{pretix_qid_region},
                answer   => $group->{region},
            },
        );

        $accreditation = $c->config->{'products_' . $group->{role}};

        if ( $group->{role} eq 'member' ) {
            $args->{email} = $claims->{preferred_username}
                           .'@'. $c->config->{mail_domain};
        }
        else {
            $args->{email} = $claims->{email};
        }
    }

    # kontrola duplicity
    my $exists = $c->schema->resultset('Order')->search({
        deleted => undef,
        email   => $args->{email},
        api     => $url,
    })->count;

    $exists ||= $c->schema->resultset('Order')->search({
        deleted  => undef,
        sso_uuid => $claims->{sub},
        api      => $url,
    })->count if $claims;

    return $c->error(400, 'Duplicity' ) if $exists && ! $c->cfg->{pretix_testmode};

    # korekce akreditace
    PRODUCT:
    foreach my $product ( @{ $args->{products} } ) {

        $product->{id} = $c->mapped_product_id($product->{id});

        next PRODUCT if $pt->{subsidy}{$product->{id}} && ! $product->{price};

        if ( $pt->{main}{ $product->{id} } ) {
            next PRODUCT if $accredited++; # neumoznit dvoji akreditace

            if ( $accreditation ) {
                $product->{id} = $accreditation;
            }
            elsif ( $pt->{nonfree}{ $product->{id} } ) {
                $product->{id} = $c->config->{products_fallback};
            }
        }

        push @products, $product;

    }

    # fallback akreditace
    @products = ( { id => $c->config->{products_fallback} }, @products ) if ! $accredited;

    my $order = {
        email            => $args->{email},
        locale           => 'en',
        payment_provider => 'manual',
        testmode         => $c->config->{pretix_testmode} ? 'true' : 'false',
        positions        => [],
        fees             => [],
    };

    # nahrani fotky
    if ( $group->{role} eq 'member' ) {
        my $photo_id = $c->_upload_photo($claims->{preferred_username});
        if ( $photo_id ) {
            push @answers, (
                {
                    question => $c->config->{pretix_qid_photo} ,
                    answer   => $photo_id,
                },
            );
        }
    }

    # ukladani do lokalni databazi
    my $local_order = $c->schema->resultset('Order')->create({
        ip       => $c->tx->remote_address,
        sso_uuid => $claims->{sub},
        email    => $order->{email},
        api      => $url,
    });

    # VS
    push @answers, (
        {
            question => $c->config->{pretix_qid_vs} ,
            answer   => $local_order->id,
        },
    );

    PRODUCT:
    foreach my $product ( @products ) {

        my $item = {
            item          => $product->{id},
            variation     => $c->mapped_variation_id($product->{variation}),
            attendee_name => $args->{name},
            price         => $product->{price},
            company       => $args->{company},
        };

        $item->{answers} = \@answers if $pt->{main}{$product->{id}};

        push @{ $order->{positions} }, $item;
    }

    $c->trace($order);

    # odeslani do pretixu
    my $ua = Mojo::UserAgent->new;
    my $rc = $ua->post( $url,
        { Authorization => 'Token ' . $c->config->{pretix_token} },
        json => $order
    )->result;

    if (! $rc->is_success) {
        $local_order->delete;
        return $c->error(400, $rc->body)
    }

    $local_order->update({
        request  => $order,
        response => $rc->json,
        order_id => $rc->json->{code},
    });

    $c->render(
        status => 201,
        json   => {
            %{ $rc->json },
            payment_request => $c->_payment_request( $local_order ),
        }
    );
}

sub exists ($c ) {
    $c->openapi->valid_input or return;

    my $key = $c->param('key');

    my $field = ( $key =~ /\@/ ) ? 'email' : 'sso_uuid';

    my $exists = $c->schema->resultset('Order')->search({
        deleted => undef,
        $field  => $key,
    })->count;

    $c->render(
        status => 200,
        openapi => { exists => $exists },
    );
}

sub get ($c ) {
    $c->openapi->valid_input or return;

    $c->oauth_token($c->tx->req->content->headers->header('X-Token'));

    my $claims = $c->oauth_claims;
    return $c->error(403, 'Invalid token') if ! $claims;

    my $order = $c->schema->resultset('Order')->search({
        deleted  => undef,
        sso_uuid => $claims->{sub},
    })->first;

    return $c->error(404, 'NOT FOUND') if ! $order;

    # data z pretixu (kvuli stavu, jinak je to v databazi???)

    $c->app->log->error($order->api . $order->order_id);
    my $ua = Mojo::UserAgent->new;
    my $rc = $ua->get( $order->api . $order->order_id . '/',
        { Authorization => 'Token ' . $c->config->{pretix_token} },
    )->result;

    return $c->error(400, $rc->body) if ! $rc->is_success;

    $c->render(
        status => 200,
        openapi => { order => $rc->json },
    );
}

sub payment ($c ) {
    my $order;

    if ( $c->stash->{id} =~ /\D/) {
        $order = $c->schema->resultset('Order')->search({
            order_id  => {ilike => $c->stash->{id}},
            deleted  => undef,
        })->first;
    }
    else {
        $order = $c->schema->resultset('Order')->find({
            id  => $c->stash->{id},
            deleted  => undef,
        });
    }
    return $c->error(404, 'NOT FOUND') if ! $order;

    my $url = sprintf ('%s/organizers/%s/events/%s/orders/%s/',
        $c->config->{pretix_api},
        $c->config->{pretix_organizer},
        $c->config->{pretix_event},
        $order->order_id,
    );

    my $ua = Mojo::UserAgent->new;
    my $rc = $ua->get( $url,
        { Authorization => 'Token ' . $c->config->{pretix_token} },
    )->result;

    my $order_pretix = $rc->json;

    $order->update({ response => $order_pretix });

    my $pr = $c->_payment_request($order);

    if ( $order_pretix->{status} eq 'p' ) {
        $pr->{payed} = $order_pretix->{payments}[0]{payment_date};
    }

    $c->render(
        status => 200,
        json   => $pr,
    );
}

sub qr ($c) {
    my $order;

    if ( $c->stash->{id} =~ /\D/) {
        $order = $c->schema->resultset('Order')->search({
            order_id  => {ilike => $c->stash->{id}},
            deleted  => undef,
        })->first;
    }
    else {
        $order = $c->schema->resultset('Order')->find({
            id  => $c->stash->{id},
            deleted  => undef,
        });
    }

    return $c->error(404, 'NOT FOUND') if ! $order;

    my $pr = $c->_payment_request($order);

    my $payment = sprintf(TPL_PAYMENT,
        $pr->{iban},
        $pr->{amount},
        $pr->{message},
        $pr->{payer},
        $order->order_id,
        $pr->{vs},
        $pr->{ss},
        $pr->{ks},
    );

    $c->trace($payment);

    my $png = qrpng (text => $payment, level => 4);

    $c->res->headers->content_type('image/png');
    $c->render( data => $png );
}

sub _upload_photo ($c, $username ) {

    my $ua = Mojo::UserAgent->new;
    my $rc = $ua->get(
        $c->config->{piratar_url} . $username . '.jpg'
    )->result;

    return if ! $rc->is_success;

    my $photo = "/tmp/$username.jpg";
    $rc->save_to($photo);

    my $tx = $ua->build_tx(POST => $c->config->{pretix_api} . '/upload', {
        Authorization         => 'Token ' . $c->config->{pretix_token},
        'Content-type'        => 'image/jpeg',
        'Content-Disposition' => qq{attachment; filename="$username.jpg"},
    });

    $tx->req->content->asset(Mojo::Asset::File->new(path => $photo));

    $rc = $ua->start($tx)->result;

    return if ! $rc->is_success;

    return $rc->json->{id};

}

sub _payment_request($c, $order) {

    my $ks_bits = 0;

    PRODUCT:
    foreach my $pos ( @{ $order->response->{positions}} ) {
        $ks_bits |= $c->payment_ks_bit($pos->{item});
    }

    return {
        account => $c->cfg->{payment_account},
        iban    => $c->cfg->{payment_iban},
        amount  => $order->response->{payments}[0]{amount},
        message => $c->cfg->{payment_msg},
        payer   => $order->request->{positions}[0]{attendee_name},
        vs      => sprintf(TPL_VS, $c->cfg->{payment_vs_prefix}, $order->id ),
        ss      => $c->cfg->{payment_ss},
        ks      => $c->cfg->{payment_ks_base} + $ks_bits,
        qr      => '/api/orders/' . $order->id . '/payment.png',
    };
}

1;