#include "AdvancedAimAssistComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
#include "GameFramework/Character.h"
#include "GameFramework/PlayerController.h"
#include "DrawDebugHelpers.h"
#include "Math/UnrealMathUtility.h"

UAdvancedAimAssistComponent::UAdvancedAimAssistComponent()
{
    PrimaryComponentTick.bCanEverTick = true;
    PrimaryComponentTick.bStartWithTickEnabled = true;
    PrimaryComponentTick.TickInterval = 0.0f; 
    
    MapVisibilityRanges.Add(TEXT("Erangel"), 80000.0f);  
    MapVisibilityRanges.Add(TEXT("Miramar"), 100000.0f); 
    MapVisibilityRanges.Add(TEXT("Sanhok"), 50000.0f);  
    MapVisibilityRanges.Add(TEXT("Vikendi"), 70000.0f);  
    MapVisibilityRanges.Add(TEXT("Livik"), 40000.0f);   
}

void UAdvancedAimAssistComponent::BeginPlay()
{
    Super::BeginPlay();
    UpdateMapSpecificSettings();
}

void UAdvancedAimAssistComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

    AntiDetectionMeasures();
    
    APlayerController* PC = GetPlayerController();
    if (!PC) return;

    if (GetWorld()->GetTimeSeconds() < LastAimTime + NextRandomDelay) return;

    ACharacter* BestTarget = FindBestTarget();
    if (!BestTarget) return;

    FRotator PlayerRotation;
    FVector PlayerLocation;
    PC->GetPlayerViewPoint(PlayerLocation, PlayerRotation);

    FVector TargetLocation = CalculatePredictedPosition(BestTarget);

    FRotator TargetRotation = UKismetMathLibrary::FindLookAtRotation(PlayerLocation, TargetLocation);

    SimulateHumanInput(TargetRotation);

    float Smoothness = CalculateAimSmoothness();
    FRotator NewRotation = FMath::RInterpTo(
        PlayerRotation,
        TargetRotation,
        DeltaTime,
        Smoothness
    );

    PC->SetControlRotation(NewRotation);
    
    LastAimTime = GetWorld()->GetTimeSeconds();
    NextRandomDelay = FMath::FRandRange(0.05f, 0.3f); 
}

void UAdvancedAimAssistComponent::AntiDetectionMeasures()
{
    if (FMath::RandRange(0, 100) > 95) 
    {
        RandomizeBehavior();
    }

    static float MapCheckTimer = 0.0f;
    MapCheckTimer += GetWorld()->GetDeltaSeconds();
    if (MapCheckTimer > 5.0f)
    {
        UpdateMapSpecificSettings();
        MapCheckTimer = 0.0f;
    }
}

void UAdvancedAimAssistComponent::RandomizeBehavior()
{
    if (FMath::RandRange(0, 100) > 70)
    {
        AimMode = static_cast<EAimAssistMode>(FMath::RandRange(0, 2));
    }

    BaseAimStrength = FMath::Clamp(
        BaseAimStrength + FMath::FRandRange(-0.1f, 0.1f),
        0.3f,
        1.2f
    );
}

void UAdvancedAimAssistComponent::SimulateHumanInput(FRotator& TargetRotation)
{
    HumanInputSimulationTimer += GetWorld()->GetDeltaSeconds();
    
    if (HumanInputSimulationTimer > 0.5f)
    {
        LastHumanInput = FRotator(
            FMath::FRandRange(-1.0f, 1.0f),
            FMath::FRandRange(-1.5f, 1.5f),
            0.0f
        );
        HumanInputSimulationTimer = 0.0f;
    }

    TargetRotation += LastHumanInput * (1.0f - BaseAimStrength * 0.5f);
}

ACharacter* UAdvancedAimAssistComponent::FindBestTarget() const
{
    TArray<ACharacter*> ValidTargets = GetAllValidTargets();
    if (ValidTargets.Num() == 0) return nullptr;

    ACharacter* BestTarget = nullptr;
    float BestScore = -1.0f;

    APlayerController* PC = GetPlayerController();
    ACharacter* PlayerChar = GetPlayerCharacter();
    if (!PC || !PlayerChar) return nullptr;

    FRotator PlayerRotation;
    FVector PlayerLocation;
    PC->GetPlayerViewPoint(PlayerLocation, PlayerRotation);
    FVector PlayerForward = PlayerRotation.Vector();

    for (ACharacter* Target : ValidTargets)
    {
        float Distance = FVector::Dist(PlayerChar->GetActorLocation(), Target->GetActorLocation());
        float Angle = FMath::RadiansToDegrees(FMath::Acos(
            FVector::DotProduct(PlayerForward, 
            (Target->GetActorLocation() - PlayerLocation).GetSafeNormal())
        ));

        FHitResult HitResult;
        FCollisionQueryParams QueryParams;
        QueryParams.AddIgnoredActor(PlayerChar);
        GetWorld()->LineTraceSingleByChannel(
            HitResult,
            PlayerLocation,
            Target->GetActorLocation(),
            ECC_Visibility,
            QueryParams
        );

        if (HitResult.GetActor() != Target) continue;

        float DistanceScore = 1.0f - FMath::Min(Distance / GetCurrentMapVisibilityRange(), 1.0f);
        float AngleScore = 1.0f - FMath::Min(Angle / 45.0f, 1.0f); 
        float PriorityScore = GetTargetPriority(Target);
        
        float TotalScore = (AngleScore * 0.5f) + (DistanceScore * 0.3f) + (PriorityScore * 0.2f);

        if (TotalScore > BestScore)
        {
            BestScore = TotalScore;
            BestTarget = Target;
        }
    }

    return BestTarget;
}

TArray<ACharacter*> UAdvancedAimAssistComponent::GetAllValidTargets() const
{
    TArray<ACharacter*> ValidTargets;
    TArray<AActor*> AllCharacters;
    UGameplayStatics::GetAllActorsOfClass(GetWorld(), ACharacter::StaticClass(), AllCharacters);

    ACharacter* PlayerChar = GetPlayerCharacter();
    if (!PlayerChar) return ValidTargets;

    for (AActor* Actor : AllCharacters)
    {
        ACharacter* Character = Cast<ACharacter>(Actor);
        if (Character && IsValidTarget(Character) && Character != PlayerChar)
        {
            ValidTargets.Add(Character);
        }
    }

    return ValidTargets;
}

bool UAdvancedAimAssistComponent::IsValidTarget(ACharacter* Target) const
{
    if (!Target) return false;
    
    return true;
}

float UAdvancedAimAssistComponent::CalculateAimSmoothness() const
{
    float BaseSmoothness = 5.0f;
    
    switch (AimMode)
    {
    case EAimAssistMode::Silent:
        return BaseSmoothness * (0.5f + BaseAimStrength * 0.5f);
    case EAimAssistMode::Standard:
        return BaseSmoothness * (1.0f + BaseAimStrength);
    case EAimAssistMode::Aggressive:
        return BaseSmoothness * (2.0f + BaseAimStrength * 2.0f);
    default:
        return BaseSmoothness;
    }
}

FVector UAdvancedAimAssistComponent::CalculatePredictedPosition(ACharacter* Target) const
{
    if (!Target) return FVector::ZeroVector;
    
    APlayerController* PC = GetPlayerController();
    ACharacter* PlayerChar = GetPlayerCharacter();
    if (!PC || !PlayerChar) return Target->GetActorLocation();

    float Distance = FVector::Dist(PlayerChar->GetActorLocation(), Target->GetActorLocation());
    float TimeToHit = Distance / 80000.0f;

    FVector PredictedPosition = Target->GetActorLocation() + (Target->GetVelocity() * TimeToHit);

    if (AimMode == EAimAssistMode::Silent)
    {
        PredictedPosition += FVector(
            FMath::FRandRange(-50.0f, 50.0f),
            FMath::FRandRange(-50.0f, 50.0f),
            FMath::FRandRange(-20.0f, 20.0f)
        );
    }

    return PredictedPosition;
}

float UAdvancedAimAssistComponent::GetTargetPriority(ACharacter* Target) const
{
    return 0.5f; 
}

void UAdvancedAimAssistComponent::UpdateMapSpecificSettings()
{
    FString MapName = GetWorld()->GetMapName();
    MapName.RemoveFromStart(GetWorld()->StreamingLevelsPrefix);

    if (MapName.Contains("Sanhok") || MapName.Contains("Livik"))
    {
        BaseAimStrength = FMath::Clamp(BaseAimStrength - 0.1f, 0.4f, 1.0f);
        AimMode = EAimAssistMode::Silent;
    }
    else
    {
        BaseAimStrength = 0.8f;
    }
}

float UAdvancedAimAssistComponent::GetCurrentMapVisibilityRange() const
{
    FString MapName = GetWorld()->GetMapName();
    MapName.RemoveFromStart(GetWorld()->StreamingLevelsPrefix);

    for (const auto& Pair : MapVisibilityRanges)
    {
        if (MapName.Contains(Pair.Key))
        {
            return Pair.Value;
        }
    }

    return 50000.0f;
}

APlayerController* UAdvancedAimAssistComponent::GetPlayerController() const
{
    return Cast<APlayerController>(GetOwner());
}

ACharacter* UAdvancedAimAssistComponent::GetPlayerCharacter() const
{
    APlayerController* PC = GetPlayerController();
    return PC ? Cast<ACharacter>(PC->GetPawn()) : nullptr;
}