package LasTres::Schema::Result::PJ;

use v5.36.0;

use strict;
use warnings;

use feature 'signatures';
use parent 'DBIx::Class::Core';
use utf8;

use UUID::URandom qw/create_uuid_string/;
use List::AllUtils;
use Data::Dumper;

use JSON qw/to_json/;

use Moo;

__PACKAGE__->table('player_pjs');

__PACKAGE__->add_columns(
    uuid => {
        data_type   => 'uuid',
        is_nullable => 0,
    },
    owner => {
        data_type      => 'uuid',
        is_nullable    => 0,
        is_foreign_key => 1,
    },
    full_name => {
        data_type   => 'text',
        is_nullable => 0,
    },
    short_name => {
        data_type   => 'text',
        is_nullable => 0,
    },
    nick => {
        data_type   => 'text',
        is_nullable => 0,
    },
    race => {
        data_type   => 'text',
        is_nullable => 0,
        accessor    => "race_string",
    },
    team => {
        data_type   => 'uuid',
        is_nullable => 0,
    },
    creation_date => {
        data_type     => 'timestamp',
        default_value => \'NOW()',
        is_nullable   => 0,
    },
    last_activity => {
        data_type     => 'timestamp',
        default_value => \'NOW()',
        is_nullable   => 0,
    },
    experience => {
        data_type     => 'integer',
        default_value => \'1',
        is_nullable   => 0,
    },
    equipment => {
        data_type      => 'uuid',
        is_nullable    => 0,
        is_foreign_key => 1,
    },
    born_stats => {
        data_type      => 'uuid',
        is_foreign_key => 1,
        is_nullable    => 0,
    },
    training_stats => {
        data_type      => 'uuid',
        is_foreign_key => 1,
        is_nullable    => 0,
    },
    skills => {
        data_type      => 'uuid',
        is_nullable    => 0,
        is_foreign_key => 1,
    },
    spells => {
        data_type      => 'uuid',
        is_nullable    => 0,
        is_foreign_key => 1,
    },
    inventory => {
        data_type   => 'uuid',
        is_nullable => 0,
    },
    health => {
        data_type   => 'integer',
        accessor    => '_health',
        is_nullable => 0,
    },
    mana => {
        data_type   => 'integer',
        accessor    => '_mana',
        is_nullable => 0,
    },
    combat_target => {
        data_type   => 'uuid',
        is_nullable => 1,
    },
    combat_action => {
        data_type   => 'text',
        is_nullable => 1,
    },
);

__PACKAGE__->set_primary_key('uuid');

__PACKAGE__->has_many( 'npcs', 'LasTres::Schema::Result::CompanionNPC',
    'owner' );
__PACKAGE__->has_many( 'logs', 'LasTres::Schema::Result::PJLog', 'owner' );
__PACKAGE__->has_many( 'known_places',
    'LasTres::Schema::Result::PJKnownPlaces', 'owner' );
__PACKAGE__->has_many( 'flags', 'LasTres::Schema::Result::PJFlag', 'owner' );
__PACKAGE__->belongs_to( 'born_stats',     'LasTres::Schema::Result::Stats' );
__PACKAGE__->belongs_to( 'training_stats', 'LasTres::Schema::Result::Stats' );
__PACKAGE__->belongs_to( 'inventory', 'LasTres::Schema::Result::Inventory' );
__PACKAGE__->belongs_to( 'skills', 'LasTres::Schema::Result::SkillLikeList' );
__PACKAGE__->belongs_to( 'spells', 'LasTres::Schema::Result::SkillLikeList' );
__PACKAGE__->belongs_to( 'equipment', 'LasTres::Schema::Result::Equipment' );
__PACKAGE__->belongs_to( 'team',      'LasTres::Schema::Result::Team' );
__PACKAGE__->belongs_to( 'owner',     'LasTres::Schema::Result::Player' );

sub knows_location ( $self, $location ) {
    require LasTres::Schema;
    my $schema = LasTres::Schema->Schema;
    my $array  = $location->to_array;
    my ( $planet, $super_area, $area ) = @$array[ 0 .. 2 ];
    $location = $array->[3];
    my @places = $schema->resultset('PJKnownPlaces')->search(
        {
            owner      => $self->uuid,
            planet     => $planet,
            super_area => $super_area,
            area       => $area,
            location   => $location
        }
    );
    if (@places) {
        return 1;
    }
    return 0;
}

sub set_known_location ( $self, $location ) {
    require LasTres::Schema;
    my $array = $location->to_array;
    my ( $planet, $super_area, $area ) = @$array[ 0 .. 2 ];
    $location = $array->[3];
    my $schema = LasTres::Schema->Schema;
    $schema->resultset('PJKnownPlaces')->new(
        {
            owner      => $self->uuid,
            planet     => $planet,
            super_area => $super_area,
            area       => $area,
            location   => $location
        }
    )->insert;
}

sub hash ($self) {
    my $image;
    my $race = $self->race;
    if ( $race->can('image') ) {
        $image = $race->image;
    }
    return {
        uuid                              => $self->uuid,
        full_name                         => $self->full_name,
        short_name                        => $self->short_name,
        nick                              => $self->nick,
        race                              => $self->race,
        health                            => $self->health,
        max_health                        => $self->max_health,
        mana                              => $self->mana,
        level                             => $self->level,
        experience_to_next_level_complete =>
          $self->experience_to_next_level_complete,
        experience_to_next_level_current =>
          $self->experience_to_next_level_current,
        max_mana => $self->max_mana,
        (
              ( defined $image )
            ? ( image => $image )
            : ()
        ),
    };
}

sub experience_to_next_level_complete ($self) {
    return ( $self->level + 1 )**3 - $self->level**3;
}

sub experience_to_next_level_current ($self) {
    return $self->experience - $self->level**3;
}

my $columns = __PACKAGE__->columns_info;
for my $column_name ( keys %$columns ) {
    my $column      = $columns->{$column_name};
    my $is_nullable = $column->{is_nullable};
    $is_nullable //= 0;
    my $required = !$is_nullable;
    if ( defined $column->{default_value} ) {
        $required = 0;
    }
    has $column_name => (
        is       => 'rw',
        required => $required,
        accessor => "_moo_$column_name",
    );
}

sub last_50_log ($self) {
    return $self->logs->search( {},
        { limit => 50, order_by => { -desc => 'date' } } );
}

sub update_team_sprites ($self) {
    require LasTres::Redis;
    my $redis = LasTres::Redis->new;
    $redis->publish( $redis->pj_subscription($self),
        to_json( { command => 'update-team-sprites' } ) );
}

sub update ($self) {
    $self->SUPER::update();
    if ( !defined $self->team->battle && $self->team->is_defeated ) {

        # Out of combat blackout.
        $self->team->on_blackout();
    }
}

sub on_level_up ( $self, $old_level ) {
    my $old_experience = $self->experience;
    my $team           = $self->team;
    my $enemy_team     = $self->_enemy_team;
    $self = $self->get_from_storage;
    $self->health( $self->max_health );
    $self->update;
    for my $member ( $team->combat_members->@* ) {
        $member->update_team_sprites;
        $member->append_log_line(
            [
                {
                    text  => $self->nick,
                    color => 'green',
                },
                {
                    text => ' subió al nivel ' . $self->level
                }
            ]
        );
    }
    if ( defined $enemy_team ) {
        for my $member ( $enemy_team->combat_members->@* ) {
            $member->update_team_sprites;
            $member->append_log_line(
                [
                    {
                        text  => $self->nick,
                        color => 'red',
                    },
                    {
                        text => ' subió al nivel ' . $self->level
                    }
                ]
            );
        }
    }
}

sub _enemy_team ($self) {
    my $team = $self->team;
    my $enemy_team;
    my $current_battle = $team->current_battle;
    {
        if ( !defined $current_battle ) {
            next;
        }
        my $battle = LasTres::Battle->get_redis($current_battle);
        if ( !defined $battle ) {
            next;
        }
        $enemy_team = $battle->get_enemy_team($team);
    }
    return $enemy_team;
}

sub gain_experience ( $self, $experience_to_gain ) {
    $experience_to_gain = int($experience_to_gain);
    my $redis          = LasTres::Redis->new;
    my $old_experience = $self->experience;
    my $old_level      = $self->level;
    my $team           = $self->team;
    my $current_battle = $team->current_battle;
    $self->experience( $old_experience + $experience_to_gain );
    $self->update;

    for my $member ( $team->combat_members->@* ) {
        $member->append_log_line(
            [
                {
                    text  => $self->nick,
                    color => 'green',
                },
                {
                    text => " ganó $experience_to_gain puntos de experiencia."
                }
            ]
        );
    }
    my $enemy_team = $self->_enemy_team;
    if ( defined $enemy_team ) {
        for my $member ( $enemy_team->combat_members->@* ) {
            $member->append_log_line(
                [
                    {
                        text  => $self->nick,
                        color => 'red',
                    },
                    {
                        text =>
                          " ganó $experience_to_gain puntos de experiencia."
                    }
                ]
            );
        }
    }
    if ( $old_level != $self->level ) {
        $self->on_level_up($old_level);
    }
}

sub append_log_line ( $self, $content ) {
    require LasTres::Schema;
    require LasTres::Redis;
    my $redis = LasTres::Redis->new;
    if ( ref $content ne 'ARRAY' ) {
        die 'Bad log content, not a arrayref.';
    }
    for my $section (@$content) {
        if ( ref $section ne 'HASH' ) {
            die 'Invalid section, not a hashref.';
        }
        my @recognized_log_keys = qw/color background text/;
        if (
            List::AllUtils::any {
                my $key = $_;
                (
                    List::AllUtils::none {
                        $key eq $_
                    }
                    @recognized_log_keys
                )
            }
            keys %$section
          )
        {
            die 'The section '
              . ( Data::Dumper::Dumper $section)
              . ' has an unrecognized key';
        }
        if ( !defined $section->{text} ) {
            die 'The section has no text.';
        }
    }
    my $uuid = create_uuid_string;
    my $log =
      LasTres::Schema->Schema->resultset('PJLog')
      ->new(
        { uuid => $uuid, owner => $self->uuid, content => to_json($content) } );
    $log->insert;
    $log = $log->get_from_storage;

    $redis->publish( $redis->pj_subscription($self),
        to_json( { command => 'append-log', log => $log->hash } ) );
}

sub location ($self) {
    return $self->team->location;
}

sub set_flag ( $self, $name ) {
    require LasTres::Schema;
    my $schema           = LasTres::Schema->Schema;
    my $result_set_flags = $schema->resultset('PJFlag');
    my $flag = $result_set_flags->new( { name => $name, owner => $self->uuid } )
      ->update_or_insert;
}

sub get_flag ( $self, $name ) {
    my @flags = $self->flags->search( { name => $name } );
    if ( scalar @flags ) {
        return 1;
    }
    return 0;
}

sub clear_flag ( $self, $name ) {
    $self->flags->search( name => $name )->delete;
}

sub health {
    my $self          = shift;
    my $health_to_set = shift;
    require LasTres::Schema;
    my $schema = LasTres::Schema->Schema;
    $schema->txn_do(
        sub {
            if ( defined $health_to_set ) {
                $self->_health($health_to_set);
                $self->update;
            }
            my $health = $self->_health;
            if ( $health < 0 ) {
                $self->_health(0);
                $self->update;
            }
            if ( $health > $self->max_health ) {
                $self->_health( $self->max_health );
                $self->update;
            }
        }
    );
    return $self->_health;
}

sub mana {
    my $self        = shift;
    my $mana_to_set = shift;
    require LasTres::Schema;
    my $schema = LasTres::Schema->Schema;
    $schema->txn_do(
        sub {
            if ( defined $mana_to_set ) {
                $self->_mana($mana_to_set);
                $self->update;
            }
            my $mana = $self->_mana;
            if ( $mana < 0 ) {
                $self->_mana(0);
                $self->update;
            }
            if ( $mana > $self->max_mana ) {
                $self->_mana( $self->max_mana );
                $self->update;
            }
        }
    );
    return $self->_mana;
}

sub update_location ($self) {
    require LasTres::Redis;
    my $redis = LasTres::Redis->new;
    $redis->publish( $redis->pj_subscription($self),
        to_json( { command => 'update-location' } ) );
}

sub update_actions($self) {
    require LasTres::Redis;
    my $redis = LasTres::Redis->new;
    $redis->publish( $redis->pj_subscription($self),
        to_json( { command => 'update-actions' } ) );
}

sub actions ($self) {
    my @actions;
    $self = $self->get_from_storage;
    my $team     = $self->team;
    my $location = $team->location;
    if ( defined $team->battle ) {

        # Here should go the battle actions.
        return [];
    }
    if ( $team->is_moving ) {

        # Probably there should go the actions still doable when moving.
        return [];
    }

    # TODO: Handle explore when implemented.
    # These are static actions.
    if ( $location->is_spawn ) {
        push @actions, $location->healing_action;
    }
    my $location_actions = $location->actions($self);
    @actions = ( @actions, @$location_actions );
    return \@actions;
}
with 'LasTres::CombatCapableEntity';
1;
