<?php

namespace Modules\Seo\Controllers;

use GuzzleHttp\Client;
use Illuminate\Http\Request;;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Seo\Models\Category;
use Modules\Seo\Models\KeywordLink;

class SeoAutoLinkController extends Controller
{

    protected $perPage = 1000;
    protected $logInsertData = [];

    public function index()
    {
        return view('seo::admin.autolink', []);
    }

    public function import(Request $request)
    {
        $file = $request->file('file');

        $returnValue = [
            'status' => 'fail',
            'result' => []
        ];

        if ($file) {
            $baseName = $file->getClientOriginalName();
            $filename = date("Y-m-d_H-i-s", time()) . '_' . $baseName;
            $uploadFolder = public_path('files/import');

            if (!is_dir($uploadFolder)) {
                mkdir($uploadFolder, 0777, true);
            }

            $path = $file->move(public_path('files/import'), $filename);
            if ($path) {

                Excel::selectSheetsByIndex(0)->load($path, function ($reader) use (&$returnValue) {

                    $categories = Category::query()
                        ->select(['id', 'slug', 'name'])
                        ->get()
                        ->toArray();

                    $client = new Client(['verify' => false]);

                    $results = $reader->get();
                    $ids = [];
                    $currentLocale = env('APP_LOCALE');
                    if (empty($currentLocale)) {
                        $currentLocale = 'us';
                    }
                    
                    foreach ($results as $row) {
                        if ($row->country != $currentLocale) {
                            continue;
                        }

                        $categoryTitle = mb_strtolower($row->category);
                        $categoryId = null;

                        switch ($categoryTitle) {
                            case 'all product': $categoryId = -1; break;
                            case 'all blog': $categoryId = -2; break;
                        }

                        if (!$categoryId) {
                            $category = array_filter($categories, function($category) use ($categoryTitle) {
                                return mb_strtolower($category['name']) == $categoryTitle;
                            });
                            if ($category) {
                                $categoryId = array_first($category)['id'];
                            } else continue;
                        }

                        $id = KeywordLink::query()->insertGetId([
                            'keyword' => $row->keyword,
                            'title' => $row->title,
                            'link' => $row->url,
                            'category_id' => $categoryId,
                            'auto' => $row->replace,
                            'priority' => $row->priority,
                            'status' => $row->status
                        ]);

                        $ids[] = $id;
                    }

                    $keywordLinks = KeywordLink::query()->whereIn('id', $ids)->get();
                    foreach ($keywordLinks as $value) {
                        $this->saveLinkCategories($value);
                    }

                    $returnValue['status'] = 'successful';
                    $returnValue['result'] = $ids;

                });
            }
        }

        return $returnValue;
    }

    public function storeKeywordLinks(Request $request)
    {
        // $routeParameters = \Route::current()->parameters();
        $responseData = [
            'status' => 'fail',
            'result' => []
        ];

        $dataKeywordLinks = $request->input('keywordLinks');
        if ($dataKeywordLinks) {
            foreach ($dataKeywordLinks as $dataKeywordLink) {
                if (isset($dataKeywordLink['id']) && $dataKeywordLink['id']) {
                    $keywordLink = KeywordLink::find($dataKeywordLink['id']);
                    if ($keywordLink) {
                        $keywordLink->fill($dataKeywordLink);
                    }
                } else {
                    $keywordLink = new KeywordLink();
                    $keywordLink->fill($dataKeywordLink);
                }

                $keywordLink->save();

                // update category
                $this->saveLinkCategories($keywordLink);

                $responseData['status'] = 'successful';
                $responseData['result'][] = $keywordLink;
            }
        }

        return $responseData;
    }

    protected function saveLinkCategories($keywordLink)
    {
        if ($keywordLink->category_id > 0) {

            $category = DB::table('category')
                ->where('id', $keywordLink->category_id)
                ->first();

            if ($category) {

                $prodType = 'POS';
                $categoryIds = [$category->id];

                DB::table('keyword_link_category')->where('keyword_link_id', $keywordLink->id)->delete();
            
                if ($category->type == 'PRODUCT') {

                    $childrentCateIds = DB::table('category')
                        ->orderBy('category._lft')
                        ->where('is_hidden', 0)
                        ->where('_lft', '>', $category->_lft)
                        ->where('_rgt', '<', $category->_rgt)
                        ->pluck('id')
                        ->toArray();

                    $prodType = 'PRD';

                    if ($childrentCateIds && count($childrentCateIds)) {
                        $categoryIds = array_merge($categoryIds, $childrentCateIds); 
                    }
                }

                DB::table('keyword_link_category')->insert(array_map(function($cateId) use ($keywordLink, $prodType) {
                    return [
                        'category_id' => $cateId,
                        'keyword_link_id' => $keywordLink->id,
                        'type' => $prodType
                    ];
                }, $categoryIds));

            }

        }
    }

    public function autoLink(Request $request)
    {
        set_time_limit(3600);
        ini_set('memory_limit', '4096M');

        $id = $request->input('id');
        $keywordLink = KeywordLink::query()->where('id', $id)->first();
        $recordUpdated = 0;

        if ($keywordLink) {

            $keywordLink->is_processing = 2;
            $keywordLink->save();

            if ($keywordLink->category_id > 0) { // category
                $recordUpdated = $this->autoLinkCategory($keywordLink);
            } else {
                switch ($keywordLink->category_id) {
                    case -1: $recordUpdated = $this->autoLinkProducts($keywordLink); break; // product
                    case -2: $recordUpdated = $this->autoLinkPosts($keywordLink); break; // post
                    case -3: $recordUpdated = $this->autoLinkTags($keywordLink); break; // tag
                }
            }

            $keywordLink->is_processing = null;
            $keywordLink->save();
        }

        return [
            'status' => 'success',
            'record_updated' => $recordUpdated
        ];
    }

    public function triggerAutoLink(Request $request)
    {
        set_time_limit(3600);

        /*
            Thay link vào:
            - Nội dung, mô tả bài viết - posts
            - Nội dung, sản phẩm - products
            - Mô tả danh mục - category
            - Mô tả tag - tag
        */

        $responseData = [
            'status' => 'fail',
            'result' => []
        ];

        $posts = [];
        $products = [];
        $categories = [];
        // $tags = [];

        $keywordLinkIds = $request->input('keywordlink_ids');
        $keywordLinks = KeywordLink::whereIn('id', $keywordLinkIds)
            ->where('is_processing', '!=', 1)
            ->get();

        // update processing
        KeywordLink::whereIn('id', $keywordLinkIds)->update([
            'is_processing' => 1
        ]);

        // update to Ram
        if ($keywordLinks && count($keywordLinks)) {
            foreach ($keywordLinks as $keywordLink) {
                if ($keywordLink->category_id > 0) { // thay link cho các danh mục;
                    $this->autoLinkCategory($keywordLink, $categories);
                } else if ($keywordLink->category_id < 0) {
                    switch ($keywordLink->category_id) {
                        case -1: // Tất cả sản phẩm
                            $this->autoLinkProducts($keywordLink, $products);
                            break;
                        case -2: // Tất cả bài viết
                            $this->autoLinkPosts($keywordLink, $posts);
                            break;
                        case -3: // tất cả tags
                            $this->autoLinkTags($keywordLink, $responseData);
                            break;
                    }
                }
            }
        }

        // update to database
        if (count($categories)) {
            $responseData['result']['categories_ids'] = [];
            foreach ($categories as $category) {
                if ($category->hasChange) {
                    $affected = DB::table('category')
                        ->where('id', $category->id)
                        ->update(['description' => $category->description]);

                    if ($affected) { // LOG

                        $responseData['result']['categories_ids'][] = $category->id;

                        $data = "FROM ------ \n\n";
                        $data .= $category->old_description . "\n";
                        $data .= "TO ------ \n\n";
                        $data .= $category->description . "\n";
                        $this->pushLog('CATEGORY', $category->id, 'AUTO_LINK', $data);
                    }
                }
            }

            $categories = null;
            unset($categories); // free up ram
        }

        if (count($products)) {
            $responseData['result']['products_ids'] = [];
            foreach ($products as $product) {
                if (isset($product->hasChange) && $product->hasChange) {
                    $affected = DB::table('product')
                        ->where('id', $product->id)
                        ->update([
                            'description' => $product->description,
                            'content' => $product->content
                        ]);

                    if ($affected) { // LOG

                        $responseData['result']['products_ids'][] = $product->id;

                        $data = '';
                        if (isset($product->old_description)) {
                            $data = "DESCRIPTION FROM ------ \n\n";
                            $data .= $product->old_description . "\n";
                            $data .= "\nTO ------ \n\n";
                            $data .= $product->description . "\n";
                        }

                        if (isset($product->old_content)) {
                            $data .= "\n\nCONTENT FROM ------ \n\n";
                            $data .= $product->old_content . "\n";
                            $data .= "\nTO ------ \n\n";
                            $data .= $product->content . "\n";
                        }

                        $this->pushLog('PRODUCT', $product->id, 'AUTO_LINK', $data);
                    }
                }
            }

            $products = null;
            unset($products); // free up ram
        }

        if (count($posts)) {
            $responseData['result']['posts_ids'] = [];
            foreach ($posts as $post) {
                if (isset($post->hasChange) && $post->hasChange) {
                    $affected = DB::table('post')
                        ->where('id', $post->id)
                        ->update([
                            'description' => $post->description,
                            'content' => $post->content
                        ]);

                    if ($affected) { // LOG

                        $responseData['result']['posts_ids'][] = $post->id;

                        $data = '';
                        if (isset($post->old_description)) {
                            $data = "DESCRIPTION FROM ------ \n\n";
                            $data .= $post->old_description . "\n";
                            $data .= "\nTO ------ \n\n";
                            $data .= $post->description . "\n";
                        }

                        if (isset($post->old_content)) {
                            $data .= "\n\nCONTENT FROM ------ \n\n";
                            $data .= $post->old_content . "\n";
                            $data .= "\nTO ------ \n\n";
                            $data .= $post->content . "\n";
                        }

                        $this->pushLog('POST', $post->id, 'AUTO_LINK', $data);
                    }
                }
            }

            $posts = null;
            unset($posts); // free up ram
        }

        KeywordLink::whereIn('id', $keywordLinkIds)->update([
            'is_processing' => 0
        ]);

        $responseData['status'] = 'successful';
        return $responseData;
    }

    private function pushLog($targetType, $targetId, $eventType, $data)
    {
        $affected = false;
        if (count($this->logInsertData) >= 1000) {
            $affected = DB::table('log')->insert($this->logInsertData);
            $this->logInsertData = [];
        } else {
            $this->logInsertData[] = [
                'target_type' => $targetType,
                'target_id' => $targetId,
                'event_type' => $eventType,
                'data' => $data,
                'created_at' => date('Y-m-d H:i:s', time())
            ];
        }
        return $affected;
    }

    private function autoLinkCategory($keywordLink)
    {
        $category = Category::query()->where('id', $keywordLink->category_id)
            ->whereNotNull('description')
            ->select(['id', 'description'])
            ->first();

        if ($category) {
            $numReplace = (!empty($keywordLink->auto)) ? $keywordLink->auto : 1;
            $tempDescription = $this->replaceKeywordLink($keywordLink, $category->description, $numReplace);

            if ($tempDescription != $category->description) {

                $affected = DB::table('category')
                    ->where('id', $category->id)
                    ->update(['description' => $tempDescription]);

                if ($affected) { // LOG
                    $data = "FROM ------ \n\n";
                    $data .= $category->description . "\n";
                    $data .= "TO ------ \n\n";
                    $data .= $tempDescription . "\n";
                    $this->pushLog('CATEGORY', $category->id, 'AUTO_LINK', $data);

                    return 1;
                }

            }
        }

        return 0;
    }

    private function autoLinkProducts($keywordLink)
    {
        $page = 1;
        $lastPage = false;
        $countProductUpdated = 0;

        $maxId = DB::table('product')->max('id');
        $pageCount = ceil($maxId / $this->perPage);

        for ($i = 0; $i < $pageCount; $i ++) {
            $start = $i * $this->perPage;
            $stop = $start + $this->perPage;

            $products = DB::table('product')->select(['id', 'description', 'content'])
                ->whereNull('deleted_at')
                ->where('id', '>=', $start)
                ->where('id', '<', $stop)
                ->get();

            foreach ($products as $product) {

                if (!isset($product->old_description) && $product->description) {
                    $product->old_description = $product->description;
                }

                if (!isset($product->old_content) && $product->content) {
                    $product->old_content = $product->content;
                }

                $numReplace = (!empty($keywordLink->auto)) ? $keywordLink->auto : 1;
                $tempDescription = $this->replaceKeywordLink($keywordLink, $product->description, $numReplace);
                $tempContent = $this->replaceKeywordLink($keywordLink, $product->content, $numReplace);

                $hasChangeDescription = false;
                if ($tempDescription != $product->description) {
                    $product->description = $tempDescription;
                    $hasChangeDescription = true;
                }

                $hasChangeContent = false;
                if ($tempContent != $product->content) {
                    $product->content = $tempContent;
                    $hasChangeContent = true;
                }

                if (($hasChangeDescription || $hasChangeContent) && $this->updateProduct($product, $hasChangeDescription, $hasChangeContent)) {
                    $countProductUpdated ++;
                }
            }

        }

        return $countProductUpdated;
    }

    private function updateProduct($product, $descriptionHasChange, $contentHasChange)
    {
        $updateData = [];
        if ($descriptionHasChange) {
            $updateData['description'] = $product->description;
        }
        if ($contentHasChange) {
            $updateData['content'] = $product->content;
        }

        $affected = DB::table('product')->where('id', $product->id)->update($updateData);

        if ($affected) { // LOG
            $data = '';

            if (isset($product->old_description) && $descriptionHasChange) {
                $data = "DESCRIPTION FROM ------ \n\n";
                $data .= $product->old_description . "\n";
                $data .= "\nTO ------ \n\n";
                $data .= $product->description . "\n";
            }

            if (isset($product->old_content) && $contentHasChange) {
                $data .= "\n\nCONTENT FROM ------ \n\n";
                $data .= $product->old_content . "\n";
                $data .= "\nTO ------ \n\n";
                $data .= $product->content . "\n";
            }

            $this->pushLog('PRODUCT', $product->id, 'AUTO_LINK', $data);

            return true;
        }

        return false;
    }

    private function autoLinkPosts($keywordLink)
    {
        $posts = DB::table('post')->select(['id', 'description', 'content'])
            ->get();

        $countPostUpdated = 0;

        foreach ($posts as $post) {

            if (!isset($post->old_description) && $post->description) {
                $post->old_description = $post->description;
            }

            if (!isset($post->old_content) && $post->content) {
                $post->old_content = $post->content;
            }

            $numReplace = (!empty($keywordLink->auto)) ? $keywordLink->auto : 1;

            $tempDescription = '';
            if($post->description) {
                $tempDescription = $this->replaceKeywordLink($keywordLink, $post->description, $numReplace);
            }

            $tempContent = '';
            if($post->content) {
                $tempContent = $this->replaceKeywordLink($keywordLink, $post->content, $numReplace);
            }

            $hasChangeDescription = false;
            if ($tempDescription != $post->description) {
                $post->description = $tempDescription;
                $hasChangeDescription = true;
            }

            $hasChangeContent = false;
            if ($tempContent != $post->content) {
                $post->content = $tempContent;
                $hasChangeContent = true;
            }

            if (($hasChangeDescription || $hasChangeContent) && $this->updatePost($post, $hasChangeDescription, $hasChangeContent)) {
                $countPostUpdated ++;
            }
        }

        return $countPostUpdated;
    }

    private function updatePost($post, $hasChangeDescription, $hasChangeContent)
    {
        $updateData = [];
        if ($hasChangeDescription) {
            $updateData['description'] = $post->description;
        }
        if ($hasChangeContent) {
            $updateData['content'] = $post->content;
        }

        $affected = DB::table('post')->where('id', $post->id)->update($updateData);

        if ($affected) { // LOG
            $data = '';
            if (isset($post->old_description)) {
                $data = "DESCRIPTION FROM ------ \n\n";
                $data .= $post->old_description . "\n";
                $data .= "\nTO ------ \n\n";
                $data .= $post->description . "\n";
            }

            if (isset($post->old_content)) {
                $data .= "\n\nCONTENT FROM ------ \n\n";
                $data .= $post->old_content . "\n";
                $data .= "\nTO ------ \n\n";
                $data .= $post->content . "\n";
            }

            $this->pushLog('POST', $post->id, 'AUTO_LINK', $data);

            return true;
        }

        return false;
    }

    private function autoLinkTags($keywordLink)
    {
        $page = 1;
        $lastPage = false;
        $countTagUpdated = 0;

        do {

            $tags = DB::table('tag')->select(['id', 'description'])
                ->whereNotNull('description')
                ->paginate($this->perPage, ['*'], 'page', $page);

            foreach ($tags as $tag) {

                if ($tag->description) {

                    $numReplace = (!empty($keywordLink->auto)) ? $keywordLink->auto : 1;
                    $tempDescription = $this->replaceKeywordLink($keywordLink, $tag->description, $numReplace);

                    if ($tempDescription != $tag->description) {

                        $affected = DB::table('tag')
                            ->where('id', $tag->id)
                            ->update([
                                'description' => $tempDescription
                            ]);

                        if ($affected) { // LOG
                            $responseData['result']['tags_ids'][] = $tag->id;
                            $data = "FROM ------ \n\n";
                            $data .= $tag->description . "\n";
                            $data .= "TO ------ \n\n";
                            $data .= $tempDescription . "\n";
                            $this->pushLog('TAG', $tag->id, 'AUTO_LINK', $data);

                            $countTagUpdated ++;
                        }

                    }
                }

            }

            if (!$lastPage) $lastPage = $tags->lastPage();
            $page ++;

        } while ($page <= $lastPage);

        return $countTagUpdated;
    }

    private function replaceKeywordLink($keywordLink, $content, $numReplace)
    {
        $keywordLinkData = $keywordLink->toArray();
        $keywordLinkKeyword = preg_quote($keywordLinkData['keyword'], '/');
        $draft  = preg_replace("/<a\b[^>]* data-keyword-link=\"" . $keywordLinkData['id'] . "\"[^>]*>(.+?)<\/a>/is", "$1", $content);
        $draft  = preg_replace("/<a\b[^>]* data-keyword-link='" . $keywordLinkData['id'] . "'[^>]*>(.+?)<\/a>/is", "$1", $draft);

        $countLink = $this->getCountKeywordLink($draft, $keywordLinkData['id']);

        $headingRegex = "/(<h.*?>)(.*?)(<\/h.*?>)/si";
        $draft = inSpecCharByRegex($draft, $headingRegex);
        // split heading tag for ignore
        $ignoreKeywords = getIgnoreKeywords($draft);
        // split a tag for ignore
        $aRegex = "/(<a\b[^>]*>)(.+?)(<\/a>)/si";
        $draft = inSpecCharByRegex($draft, $aRegex);
        $keyword = htmlentities($keywordLinkKeyword, ENT_QUOTES, 'UTF-8');

        if ($numReplace - $countLink > 0 && !in_array(strtolower($keywordLinkKeyword), $ignoreKeywords) && $keywordLinkData['status'] == "enable") {
            $replaceString = "<a href=\"" . $keywordLinkData['link'] . "\" title=\"" . htmlentities($keywordLinkData['title']) . "\" data-keyword-link=\"" . $keywordLinkData['id'] . "\">$1</a>";
            try {
                $contentDraft = preg_replace("/(?!(?:[^<\[]+[>\]]|[^>\]]+<\/a>))\b(" . $keyword . ")/imsUu", $replaceString, $draft, $numReplace - $countLink, $countReplace);
                if ($numReplace - $countReplace > 0) {
                    $contentDraft = preg_replace("/(?!(?:[^<\[]+[>\]]|[^>\]]+<\/a>))\b(" . $keywordLinkData['keyword'] . ")\b/imsUu", $replaceString, $contentDraft, $numReplace - $countReplace);
                }
            } catch (\Exception $ex) {
                Log::info('error_auto_link', [$ex->getMessage() . ' line: ' . $ex->getLine() . ' keyword: ' . $keyword]);
                $contentDraft = $draft;
            }
        } else {
            $contentDraft = $draft;
        }

        $contentDraft = reSpecCharByRegex($contentDraft, $headingRegex);
        $contentDraft = reSpecCharByRegex($contentDraft, $aRegex);

        return stripslashes($contentDraft);
    }

    private function getCountKeywordLink($draft, $autoLinkId)
    {
        $retVal = 0;
        preg_match_all("/<a\b[^>]* data-keyword-link=\"$autoLinkId\">(.+?)<\/a>/is", $draft, $matches);
        if (isset($matches[1]) && count($matches[1]) > 0) {
            $listKeywordLinkId = array_unique($matches[1]);
            $retVal = count($listKeywordLinkId);
        }
        return $retVal;
    }
}
