<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Http\Traits\Timezone;

class Member extends Model
{
    const CUSTOMER_CUSTOMER = 1;
    const STATUS_DELETED = 'deleted';

    use HasFactory, Timezone;

    public $allSponsorIds = [];
    public $depthofsp = 0;
    public $starRanks = [];
    public $pageName = "Member";
    protected $fillable = [
        'id', 'username', 'email', 'is_email_verified', 'is_kyc_verified', 'login_password', 'transaction_password', 'registered_at', 'updated_at', 'lastlogged_at', 'ccw_id', 'sponsor_id', 'sponsor_ids', 'personal_sponsor_id', 'personal_sponsor_ids', 'firstname', 'lastname', 'gender', 'birthdate', 'language_id', 'address_line_1', 'address_line_2', 'city', 'state', 'zip', 'country_id', 'company', 'phone', 'is_ccw_in_sync', 'level', 'rank', 'customer', 'abv', 'pgv', 'qbv', 'tsv', 'wtsv', 'qualification_period_start', 'qualification_period_end', 'qualification_period_number', 'status', 'reg_site', 'kyc', 'kyc_status', 'user_id',
    ];
    protected $table = "user_payran";
    protected $casts = [
        'sponsor_ids' => 'json',
        'created_at' => 'datetime:d-m-Y H:i:s'
    ];
    protected $primaryKey = 'member_id';

    public static function boot()
    {
        parent::boot();
        self::created(function ($model) {
            if (empty($model->id)){
                $model->id = $model->user_id;
            }
            $model->save();
        });
    }

    public static function updateStarRankArray()
    {
        $starrank = collect();

        $rankdata = CustomerLevel::whereNull('template_id')->where('for_what', 'Configuration parameters for Star Ranks')->first();
        $rank = json_decode($rankdata->value, true);

        foreach ($rank as $value) {

            $temp['rank'] = $value['rank'];
            $temp['minimumTsv'] = $value['minimum_tsv'];
            $temp['maximumTsvRatioPerLeg'] = $value['maximum_tsv_ratio_per_leg'];
            $temp['minimumPsu'] = $value['minimum_psu'];
            $temp['minimumPsuCustomerLevel'] = $value['minimum_psu_customer_level'];
            $temp['minimumPsuLevel'] = $value['minimum_psu_level'];
            $temp['minimumSu'] = $value['minimum_su'];
            $temp['minimumSuRank'] = $value['minimum_su_rank'];
            $temp['maximumSuPerLeg'] = $value['maximum_su_per_leg'];
            $temp['maximumUsedMatchingBonusLevel'] = $value['maximum_used_matching_bonus_level'];
            $temp['teamsiteLicenseNeeded'] = true;
            $temp['reward'] = [Wallet::TYPE_COMPULSORY => [WalletLog::STAR_RANK => $value['reward']]];
            $starrank[] = $temp;
        }
        $starrank = $starrank->sortBy('rank');
        $starrank->values()->all();
        return $starrank->toArray();
    }

    public static function getCsrAddress($userId)
    {
        $address = UserCsrAddress::where('user_id', $userId)->orderBy('id', 'desc')->limit(1)->get();
        return $address ? $address[0]['address'] : null;
    }

    public function personalSponsor()
    {
        return $this->belongsTo(self::class,'personal_sponsor_id','user_id')->with('user');

    }
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id')->with('details');
    }

    public function UserTeamsiteLicenses()
    {
        return $this->hasMany(UserTeamsiteLicense::class, 'user_id', 'id');
    }

    public function userTeamsiteLicensesHas()
    {
        return $this->hasOne(UserTeamsiteLicense::class, 'user_id', 'id')->where(
            'start',
            '<=',
            Carbon::now()
        )
            ->where(
                'end',
                '>=',
                Carbon::now()
            );
    }

    public function sponsor()
    {
        return $this->belongsTo(self::class,'sponsor_id','user_id')->with('user');
    }

    public function getDepth()
    {
        $sponsorIds = json_decode($this->attributes['sponsor_ids']) ?? [];
        if (count($sponsorIds) <= 0) {
            $this->refreshAllSponsorIds();
            $sponsorIds = $this->getAllSponsorIdsDB();
        }
        return count($sponsorIds);
    }

    public function refreshAllSponsorIds()
    {

        $this->reloadAllSponsorIds();

        $this->save();
    }

    public function reloadAllSponsorIds()
    {
        $sponsorIds = [$this->attributes['sponsor_id']];
        $sponsor = $this->sponsor;

        if ($sponsor) {
            $sponsorIds = array_merge($sponsorIds, $this->sponsor->getAllSponsorIds($this->attributes['sponsor_id']));
        }
        $this->allSponsorIds = array_values(array_unique((array_merge($this->allSponsorIds, $sponsorIds))));
        $this->attributes['sponsor_ids'] = json_encode($this->allSponsorIds);
    }

    public function getAllSponsorIds($sid)
    {
        $sponsor = Member::where('id', $sid)->first();
        if ($sponsor) {
            $this->allSponsorIds[] = $sponsor->id;
            if ($sponsor->sponsor_id) {
                $this->getAllSponsorIds($sponsor->sponsor_id);
            }
        }
        return $this->allSponsorIds;
    }

    public function getAllSponsorIdsDB()
    {
        $sponsorIds = json_decode($this->attributes['sponsor_ids']);
        if (!$sponsorIds) {
            $this->refreshAllSponsorIds();
            $sponsorIds = $this->getAllSponsorIdsDB();
        }
        return array_filter($sponsorIds);
    }

    public function getPersonalSponsorIdsAttribute()
    {
        return json_decode($this->attributes['personal_sponsor_ids']) ?? [];
    }

    public function getDymanicCompressedPersonalSponsorUpline($calculationDay, $skipStatuses, $minSponsorLevel, $minStarRank, $bv = 0)
    {
        $sponsors = $this->getAllPersonalSponsorUplines();

        foreach ($sponsors as $sponsor) {

            if (in_array($sponsor->status, $skipStatuses)) {

                continue;
            } elseif ($sponsor->level < $minSponsorLevel) {

                continue;
            } elseif ($sponsor->rank < $minStarRank) {

                continue;
            } elseif (!$this->isEligibleForCommission($sponsor, $calculationDay[0], $calculationDay[1])) {

                continue;
            }

            return $sponsor;
        }
    }

    public function getAllPersonalSponsorUplines($status = null)
    {
        $sponsorIds = $this->allPersonalSponsorUplineIds();
        $sponsors = Member::whereIn('id', $sponsorIds);

        if ($status) {
            $sponsors->where('status', $status);
        }

        $sponsors = $sponsors->pluck('id')->toArray();
        $result = [];

        foreach ($sponsorIds as $sponsorId) {
            if (in_array($sponsorId, $sponsors)) {
                $result[] = Member::where('id', $sponsorId)->first();
            }
        }

        return $result;
    }

    public function allPersonalSponsorUplineIds()
    {
        $sponsorIds = [$this->attributes['personal_sponsor_id']];
        $psu = $this->personalSponsor;

        if ($psu) {
            $getAllSponsorIdsDB_Array = $psu->getAllSponsorIdsDB();
            if (!is_array($getAllSponsorIdsDB_Array)){
                $getAllSponsorIdsDB_Array = [(int)$getAllSponsorIdsDB_Array];
            }
            $sponsorIds = array_merge($sponsorIds, $getAllSponsorIdsDB_Array);
        }

        return $sponsorIds;
    }

    public function isEligibleForCommission($sponsor, $start, $end)
    {
        return UserTeamsiteLicense::where([['user_id', $sponsor->id], ['start', '<', $start], ['end', '>', $end]])->exists();
    }

    public function getDymanicCompressedSponsor($calculationDay, $skipStatuses, $minSponsorLevel, $minStarRank, $bv = 0)
    {
        $sponsors = $this->getAllSponsors(null, $calculationDay[0], $calculationDay[1]);
        foreach ($sponsors as $sponsor) {
            if (in_array($sponsor->status, $skipStatuses)) {
                continue;
            } elseif ($sponsor->level < $minSponsorLevel) {
                continue;
            } elseif ($sponsor->rank < $minStarRank) {
                continue;
            } elseif (count($sponsor->UserTeamsiteLicenses) == 0) {
                Log::info(sprintf('Skipped %s, date issue', $sponsor->username));
                continue;
            }

            return $sponsor;
        }
    }

    public function getAllSponsors($status = null, $date1, $date2)
    {
        if (Cache::has($this->attributes['id'] . "sps")) {
            return Cache::get($this->attributes['id'] . "sps");
        } else {
            $sponsorIds = $this->getAllSponsorIdsDB();
            $sponsors = Member::with(['UserTeamsiteLicenses' => function ($q) use ($date1, $date2) {
                $q->where([['start', '<', $date1], ['end', '>', $date2]]);
            }])->whereIn('id', $sponsorIds);
            if ($status) {
                $sponsors->where('status', $status);
            }
            $sponsors = $sponsors->get()->keyBy('id');
            $result = [];
            foreach ($sponsorIds as $sponsorId) {
                if (isset($sponsors[$sponsorId])) {
                    $result[] = $sponsors[$sponsorId];
                }
            }
            Cache::put($this->attributes['id'] . "sps", $result, now()->addMinutes(240));
            return $result;
        }
    }

    public function getAllSponsorsWithoutLincenses($status = null)
    {
        if (Cache::has($this->attributes['id'] . "sps-wtd")) {
            return Cache::get($this->attributes['id'] . "sps-wtd");
        } else {
            $sponsorIds = $this->getAllSponsorIdsDB();
            $sponsors = Member::whereIn('id', $sponsorIds);
            if ($status) {
                $sponsors->where('status', $status);
            }
            $sponsors = $sponsors->get()->pluck('id')->toArray();
            $result = [];
            foreach ($sponsorIds as $sponsorId) {
                if (in_array($sponsorId, $sponsors)) {
                    $result[] = Member::where('id', $sponsorId)->first();
                }
            }
            Cache::put($this->attributes['id'] . "sps-wtd", $result, now()->addMinutes(240));
            return $result;
        }
    }

    public function refreshStarRank($starRanks)
    {
        $this->starRanks = $starRanks;
        $oldRank = $this->attributes['rank'];
        $newRank = $this->calculateStarRankForUser($this->attributes['id']);

        if ($newRank > $oldRank) {
            $this->rank = $newRank;
            $this->save();

            $log = "Start rank updated from $oldRank to $newRank";
            saveLog($log,$this,$this,'Star Rank',User::find($this->user_id));
        }

        if ($newRank > 0) {
            $log = UserQualificationLog::where([
                ['user_id', $this->id],
                ['type', 'rank'],
                ['value', $newRank],
                ['source', UserQualificationLog::SOURCE_PERFORMANCE],
            ])->first();
            if (!$log) {
                $this->createUserQualificationLog('rank', $newRank, $oldRank, UserQualificationLog::SOURCE_PERFORMANCE);
            }
        }
        for ($rank = 1; $rank <= $this->attributes['rank']; ++$rank) {
            $this->addStarRankReward($rank, Carbon::now()->toDateString());
        }
    }

    public function calculateStarRankForUser($id)
    {
        $newRank = 0;
        $rankResults = $this->getStarRankResultsForUser($id);

        foreach ($rankResults as $rank => $rankResult) {
            if ($rankResult['tsvDone'] && $rankResult['psuDone'] && $rankResult['suDone']) {

                $newRank = $rank;
            }
        }
        return $newRank;
    }

    public function getStarRankResultsForUser($id)
    {
        $user = Member::where('id', $id)->first();
        $results = [];
        // Load the active license if exists

        $hasTeamsiteLicense = $user->userTeamsiteLicensesHas ? true : false;

        $psus = Member::select('level', 'customer', DB::raw('count(*) as cnt'))
            ->where(
                'personal_sponsor_id',
                $user->id
            )
            ->groupBy('level', 'customer')
            ->get()
            ->toArray();
        $level1Sus = Member::select('id', 'tsv')
            ->where(
                'sponsor_id',
                $user->id
            )
            ->get()
            ->toArray();
        $extra = UserExtra::where([
            ['user_id', $user->id],
            ['type', UserExtra::TYPE_ADDITIONAL_TSV],
        ])->first();
        $extraTsv = $extra ? (int) $extra->value : 0;
        $specialBvs = [];
        // Determine the number of all personally sponsored users grouped by their sponsor and customer level.

        $specialPsus = Member::where(
                'personal_sponsor_id',
                $user->id
            )
            ->whereColumn('personal_sponsor_id', '!=', 'sponsor_id')
            ->get()
            ->pluck('id')
            ->toArray();

        if (!empty($specialPsus)) {
            $specialBvs = BV::select('user_id', DB::raw('SUM(value) value'))
                ->where('origin', BV::ORIGIN_PERSONAL_SPONSOR)
                ->whereIn('user_id', $specialPsus)
                ->groupBy('user_id')
                ->get()
                ->toArray();
        }

        foreach ($this->starRanks as $index => $requirements) {
            $rank = $requirements['rank'];
            $results[$rank]['hasTeamsiteLicense'] = $hasTeamsiteLicense;

            foreach ($psus as $psu) {
                if ($psu['level'] >= $requirements['minimumPsuLevel'] || $psu['customer'] >= $requirements['minimumPsuCustomerLevel']) {
                    if (!isset($results[$rank]['psu'])) {
                        $results[$rank]['psu'] = 0;
                    }
                    $results[$rank]['psu'] += $psu['cnt'];
                }
            }
            $maxTsvPerLeg = $requirements['maximumTsvRatioPerLeg'] * $requirements['minimumTsv'];
            $tsv = Member::select(DB::raw(sprintf('SUM(IF(tsv > %f, %f, tsv)) as stsv', $maxTsvPerLeg, $maxTsvPerLeg)))
                ->where(
                    'sponsor_id',
                    $user->id
                )
                ->first();

            $results[$rank]['tsv'] = $tsv ? $tsv->stsv : 0;
            foreach ($specialBvs as $specialBv) {
                if (!isset($results[$rank]['tsv'])) {
                    $results[$rank]['tsv'] = 0;
                }
                $results[$rank]['tsv'] += min($specialBv['value'], $maxTsvPerLeg);
            }
            $results[$rank]['tsv'] += min($extraTsv, $maxTsvPerLeg);
        }
        $level1SuStarRanks = [];
        // Populate container for holding sponsored user numbers by Star Rank for legs.
        foreach ($level1Sus as $su) {
            $level1SuStarRanks[$su['id']] = [
                1 => 0,
                2 => 0,
                3 => 0,
                4 => 0,
                5 => 0,
                6 => 0,
                7 => 0,
            ];
        }
        $rankedUsers = Member::where(
            'rank',
            '>',
            0
        )->get();
        $level1SuIds = array_column($level1Sus, 'id');
        $temp = [];
        // Iterate over users with Star Rank to determine which of them belongs to any legs.

        foreach ($rankedUsers as $rankedUser) {
            $sponsors = $rankedUser->getAllSponsorIdsDB();
            $sponsors[] = $rankedUser->id;
            $validSponsors = $this->intersectFast($sponsors, $level1SuIds);
            $temp[] = [
                $rankedUser->id => $validSponsors,
            ];
            // Intersect the sponsors with the legs.
            foreach ($validSponsors as $id) {
                if (isset($level1SuStarRanks[$id])) {
                    try {
                        ++$level1SuStarRanks[$id][$rankedUser->rank];
                        //code...
                    } catch (\Throwable$th) {
                        //throw $th;

                    }
                }
            }
        }

        foreach ($this->starRanks as $index => $requirements) {
            $innerRank = $requirements['rank'];
            if ($requirements['minimumSu'] > 0) {
                foreach ($level1SuStarRanks as $suRanks) {
                    $total = 0;
                    foreach ($suRanks as $suRank => $cnt) {
                        if ($suRank >= $requirements['minimumSuRank']) {
                            $total += $cnt;
                        }
                    }
                    if (!isset($results[$innerRank]['su'])) {
                        $results[$innerRank]['su'] = 0;
                    }
                    $results[$innerRank]['su'] += min($requirements['maximumSuPerLeg'], $total);
                }
            }
            $results[$innerRank]['tsv'] = isset($results[$innerRank]['tsv']) ? $results[$innerRank]['tsv'] : 0;
            $results[$innerRank]['psu'] = isset($results[$innerRank]['psu']) ? $results[$innerRank]['psu'] : 0;
            $results[$innerRank]['su'] = isset($results[$innerRank]['su']) ? $results[$innerRank]['su'] : 0;
            $results[$innerRank]['tsvDone'] = $results[$innerRank]['tsv'] >= $requirements['minimumTsv'];
            $results[$innerRank]['psuDone'] = $results[$innerRank]['psu'] >= $requirements['minimumPsu'];
            $results[$innerRank]['suDone'] = $results[$innerRank]['su'] >= $requirements['minimumSu'];
            $results[$innerRank]['teamsiteLicenseDone'] = !$requirements['teamsiteLicenseNeeded'] || $hasTeamsiteLicense;
        }
        return $results;
    }

    public static function intersectFast($array1, $array2)
    {
        $index = array_flip($array1);
        $second = array_flip($array2);
        $x = array_intersect_key($index, $second);

        return array_flip($x);
    }

    private function createUserQualificationLog($attribute, $newValue, $oldValue, $source = null)
    {
        if (is_null($source)) {
            $source = UserQualificationLog::SOURCE_PERFORMANCE;
        }
        $log = UserQualificationLog::create([
            'user_id' => $this->id,
            'type' => $attribute,
            'value' => $newValue,
            'value_before' => $oldValue,
            'source' => $source,
        ]);

        if ($source == UserQualificationLog::SOURCE_ADMIN && $attribute == 'level' && $oldValue == 0 && $newValue > 0) {
            // Ha 0-ról nőtt a level admin által, akkor adni kell Business License-t, ha nem volt még
            $licenseExpiration = $this->getBusinessLicenseExpiration();
            // Ha lejárt a meglévő Business License, akkor adjunk újat (töröljük a lejárati dátumot)
            if (!is_null($licenseExpiration) && strtotime($licenseExpiration) < time()) {
                $licenseExpiration = null;
            }
            if (is_null($licenseExpiration)) {
                UserTeamsiteLicense::create([
                    'user_id' => $this->id,
                    'type' => UserTeamsiteLicense::TYPE_12_MONTHS,
                    'start' => date('Y-m-d H:i:s'),
                    'end' => date('Y-m-d 23:59:59', strtotime('+ 12 months')),
                ]);
            }
        }

        return $log;
    }

    public function getBusinessLicenseExpiration()
    {
        $licenses = $this->userTeamsiteLicenses;
        $expire = null;
        foreach ($licenses as $license) {
            if (is_null($expire) || strtotime($license->end) > strtotime($expire)) {
                $expire = $license->end;
            }
        }

        return $expire;
    }

    public function addStarRankReward($rank, $createdAt = null)
    {
        $key = array_search($rank, array_column($this->starRanks, 'rank'));
        $reward = $this->starRanks[$key]['reward'];
        foreach ($reward as $action => $walletData) {
            foreach ($walletData as $walletType => $value) {
                Wallet::firstOrCreate([['type', $walletType], ['user_id', $this->attributes['id']]]);
                distributeBonus($this, $value, [
                    $walletType => [$action, 1],

                ], [
                    'unique_id' => $rank,
                    'created_at' => $createdAt,
                ]);
            }
        }
    }

    public function distributeBonus($bonus, $wallets)
    {
        // For every wallet a new wallet_log entry is created.
        foreach ($wallets as $type => $parameters) {
            $action = $parameters[0];
            $multiplier = $parameters[1];
            $value = $bonus * $multiplier;
            $wid = Wallet::firstOrCreate([['type', $type], ['user_id', $this->attributes['id']]]);
            WalletLog::create([
                'wallet_id' => $wid->id,
                'value' => $value,
                'action' => $action,
            ]);

        }
    }

    // public function email()
    // {
    //     return $this->belongsTo(User::class, 'email');
    // }

    public function downline()
    {
        return $this->hasMany(Member::class, 'sponsor_id', 'user_id')->with('user.details');
    }

    public function upline()
    {
        return $this->belongsTo(Member::class, 'sponsor_id', 'user_id')->with('user.details');
    }

    // Recursive relationship to get all uplines
    public function allUplines()
    {
        return $this->upline()->with('allUplines');
    }

    // Recursive relationship to get all downlines
    public function allDownlines()
    {
        return $this->downline()->with('allDownlines');
    }
}
