package LasTres::Battle;

use v5.36.0;
use strict;
use warnings;

use feature 'signatures';

use Moo;

use UUID::URandom qw/create_uuid_string/;
use LasTres::Util;
use LasTres::EnemyTeam;
use JSON qw//;

has uuid => (
    is      => 'rw',
    default => \&_build_uuid,
);

has team1 => (
    is       => 'rw',
    required => 1,
);

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

has last_frame => (
    is       => 'rw',
    required => 1,
);

sub sanity_check ($self) {
    my $team1 = $self->team1;
    my $team2 = $self->team2;
    if ( !defined $team2 ) {
        return 0;
    }
    if (   !defined $team1->current_battle
        || !defined $team2->current_battle
        || $team1->current_battle ne $self->uuid
        || $team2->current_battle ne $self->uuid )
    {
        return 0;
    }
    return 1;
}

sub winner ($self) {
    if ( $self->team2->is_defeated ) {
        return $self->team1;
    }
    if ( $self->team1->is_defeated ) {
        return $self->team2;
    }
    return;
}

sub try_turn ( $self, $entity ) {
    my $combat_target        = $entity->combat_target;
    my $enemy_team           = $self->get_enemy_team( $entity->team );
    my @combat_members_enemy = @{ $enemy_team->combat_members };
    @combat_members_enemy = grep { $_->health > 0 } @combat_members_enemy;
    if ( $entity->health <= 0 ) {

        # This entity is out of the battle.
        return;
    }
    if ( defined $entity->combat_target ) {
        {
            my @target_list_only_one_should_match =
              grep { $combat_target eq $_->uuid } @combat_members_enemy;
            if ( !scalar @target_list_only_one_should_match ) {
                undef($combat_target);
                next;
            }
            $combat_target = $target_list_only_one_should_match[0];
        }
    }
    if ( !defined $combat_target ) {
        $combat_target =
          $combat_members_enemy[ LasTres::Util::rand_range_int( 0,
              $#combat_members_enemy ) ];
        $entity->combat_target( $combat_target->uuid );
        $entity->update;
    }
    $entity->attack($combat_target);
}

sub get_enemy_team ( $self, $team ) {
    if ( $self->team1->uuid ne $team->uuid ) {
        return $self->team1;
    }
    return $self->team2;
}

sub loser ($self) {
    if ( $self->team2->is_defeated ) {
        return $self->team2;
    }
    if ( $self->team1->is_defeated ) {
        return $self->team1;
    }
    return;
}

sub end ($self) {
    require LasTres::Redis;
    my $redis = LasTres::Redis->new;
    $self->team1->current_battle(undef);
    $self->team1->update;
    if ( defined $self->team2 ) {
        $self->team2->current_battle(undef);
        $self->team2->update;
    }
    $redis->db->expire( $redis->battle_key( $self->uuid ), 0 );
    $self->team1->on_end_combat;
    $self->team2->on_end_combat;
}

sub _build_uuid {
    return create_uuid_string();
}

sub to_json ($self) {
    return {
        uuid       => $self->uuid,
        team1      => $self->team1->to_serializable,
        team2      => $self->team2->to_serializable,
        last_frame => $self->last_frame,
    };
}

sub save_redis ($self) {
    require LasTres::Redis;
    my $redis = LasTres::Redis->new;
    $redis->db->setex( $redis->battle_key( $self->uuid ),
        3600 * 6, JSON::to_json( $self->to_json ) );
}

sub get_redis ( $class, $uuid ) {
    require LasTres::Redis;
    my $redis = LasTres::Redis->new;
    my $hash_self;
    eval {
        $hash_self =
          JSON::from_json( $redis->db->get( $redis->battle_key($uuid) ) );
    };
    if ($@) {
        return;
    }
    return $class->from_json( $uuid, $hash_self );
}

sub from_json ( $class, $uuid, $hash ) {

    # I do not take the hash uuid since if redis lies that could be really bad.
    return $class->new(
        uuid       => $uuid,
        team1      => $class->deserialize_team( $hash->{team1} ),
        team2      => $class->deserialize_team( $hash->{team2} ),
        last_frame => $hash->{last_frame},
    );
}

sub deserialize_team ( $class, $team_hash ) {
    if ( $team_hash->{is_db} ) {
        return LasTres::Schema::Result::Team->from_serializable($team_hash);
    }
    return LasTres::EnemyTeam->from_serializable($team_hash);
}

sub start_battle_machine ( $class, $team, $enemy_area_array ) {
    my @enemy_area_array = @$enemy_area_array;
    my $battle = LasTres::Battle->new( team1 => $team, last_frame => 0 );
    my $team2  = LasTres::EnemyTeam->new( current_battle => $battle->uuid );
    my @enemies_array = ( map { $_->generate($team2) } @$enemy_area_array );
    my @enemy_log_list;
    my $first_log = 1;

    $team2->members( \@enemies_array );
    $battle->team2($team2);
    $battle->save_redis;
    $team->current_battle( $battle->uuid );
    for my $member ( $team2->combat_members->@* ) {
        if ( !$first_log ) {
            push @enemy_log_list, { text => ', ' };
        }
        $first_log = 0;
        push @enemy_log_list,
          {
            color => 'red',
            text  => $member->nick,
          };
        push @enemy_log_list,
          { text =>
" Nivel @{[$member->level]} Salud @{[$member->health]}/@{[$member->max_health]}"
          };
    }
    for my $member ( $team->members ) {
        $member->append_log_line(
            [
                {
                    text => 'Empieza un combate contra: '
                },
                @enemy_log_list
            ]
        );
    }
    $team->update;
    return $battle;
}
1;
