<?php
/**
 * app/plugins/bookmarks/models/bookmarks.php
 *
 * This class is used for the manipulation of the bookmarks database table.
 *
 * @package TP Bookmarks
 * @version 3.0
 * @author  Joey Kimsey <Joey@thetempusproject.com>
 * @link    https://TheTempusProject.com
 * @license https://opensource.org/licenses/MIT [MIT LICENSE]
 */
namespace TheTempusProject\Models;

use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\Canary\Classes\CustomException;

class Bookmarks extends DatabaseModel {
    public $tableName = 'bookmarks';
    public $linkTypes = [
        'Open in New Tab' => 'external',
        'Open in Same Tab' => 'internal',
    ];

    public $databaseMatrix = [
        [ 'title', 'varchar', '256' ],
        [ 'url', 'text', '' ],
        [ 'color', 'varchar', '48' ],
        [ 'privacy', 'varchar', '48' ],
        [ 'folderID', 'int', '11' ],
        [ 'description', 'text', '' ],
        [ 'createdBy', 'int', '11' ],
        [ 'createdAt', 'int', '11' ],
        [ 'meta', 'text', '' ],
        [ 'icon', 'text', '' ],
        [ 'archivedAt', 'int', '11' ],
        [ 'refreshedAt', 'int', '11' ],
        [ 'hiddenAt', 'int', '11' ],
        [ 'order', 'int', '11' ],
        [ 'linkType', 'varchar', '32' ],
        [ 'uuid', 'char', '36' ],
    ];

    /**
     * The model constructor.
     */
    public function __construct() {
        parent::__construct();
    }

    public function create( $title, $url, $folderID = 0, $description = '', $color = 'default', $privacy = 'private', $type = 'external', $user = null ) {
        if ( empty( $user ) ) {
            $user = App::$activeUser->ID;
        }
        $fields = [
            'title' => $title,
            'url' => $url,
            'description' => $description,
            'color' => $color,
            'privacy' => $privacy,
            'createdBy' => $user,
            'uuid' => generateUuidV4(),
            'createdAt' => time(),
        ];
        if ( !empty( $folderID ) ) {
            $fields['folderID'] = $folderID;
        } else {
            $fields['folderID'] = null;
        }
        if ( ! self::$db->insert( $this->tableName, $fields ) ) {
            new CustomException( 'bookmarkCreate' );
            Debug::error( "Bookmarks: not created " . var_export($fields,true) );
            return false;
        }
        return self::$db->lastId();
    }

    public function update( $id, $title, $url, $folderID = 0, $description = '', $color = 'default', $privacy = 'private', $type = 'external', $order = 0 ) {
        if ( !Check::id( $id ) ) {
            Debug::info( 'Bookmarks: illegal ID.' );
            return false;
        }
        $fields = [
            'title' => $title,
            'url' => $url,
            'description' => $description,
            'color' => $color,
            'privacy' => $privacy,
            // 'linkType' => $type,
            // 'order' => $order,
        ];
        if ( !empty( $folderID ) ) {
            $fields['folderID'] = $folderID;
        }
        if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
            new CustomException( 'bookmarkUpdate' );
            Debug::error( "Bookmarks: $id not updated" );
            return false;
        }
        return true;
    }

    public function findByUuid( $id ) {
        $whereClause = ['uuid', '=', $id];
        if ( empty( $limit ) ) {
            $bookmarks = self::$db->get( $this->tableName, $whereClause );
        } else {
            $bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
        }
        if ( !$bookmarks->count() ) {
            Debug::info( 'No Bookmarks found.' );
            return false;
        }
        return $this->filter( $bookmarks->first() );
    }

    public function byUser( $limit = null ) {
        $whereClause = ['createdBy', '=', App::$activeUser->ID];
        if ( empty( $limit ) ) {
            $bookmarks = self::$db->get( $this->tableName, $whereClause );
        } else {
            $bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
        }
        if ( !$bookmarks->count() ) {
            Debug::info( 'No Bookmarks found.' );
            return false;
        }
        return $this->filter( $bookmarks->results() );
    }

    public function unsafeByFolder( $id, $limit = null ) {
        $whereClause = [ 'folderID', '=', $id ];
        if ( empty( $limit ) ) {
            $bookmarks = self::$db->get( $this->tableName, $whereClause );
        } else {
            $bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
        }
        if ( !$bookmarks->count() ) {
            Debug::info( 'No Bookmarks found.' );
            return false;
        }
        return $this->filter( $bookmarks->results() );
    }

    public function byFolder( $id, $hiddenExcluded = false, $archivedExcluded = false ) {
        $whereClause = ['createdBy', '=', App::$activeUser->ID ];
        $whereClause = array_merge( $whereClause, [ 'AND', 'folderID', '=', $id ] );
        if ( ! empty( $hiddenExcluded ) ) {
            $whereClause = array_merge( $whereClause, [ 'AND', 'hiddenAt', 'is',  'NULL'] );
        }
        if ( ! empty( $archivedExcluded ) ) {
            $whereClause = array_merge( $whereClause, [ 'AND', 'archivedAt', 'is',  'NULL'] );
        }
        $bookmarks = self::$db->get( $this->tableName, $whereClause );
        if ( ! $bookmarks->count() ) {
            Debug::info( 'No Bookmarks found.' );
            return false;
        }
        return $this->filter( $bookmarks->results() );
    }

    public function publicByFolder( $id, $limit = null ) {
        $whereClause = ['createdBy', '=', App::$activeUser->ID, 'AND'];
        $whereClause = array_merge( $whereClause, [ 'folderID', '=', $id, 'AND', 'privacy', '=', 'public' ] );
        if ( empty( $limit ) ) {
            $bookmarks = self::$db->get( $this->tableName, $whereClause );
        } else {
            $bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
        }
        if ( !$bookmarks->count() ) {
            Debug::info( 'No Bookmarks found.' );
            return false;
        }
        return $this->filter( $bookmarks->results() );
    }

    public function noFolder( $id = 0, $limit = 10 ) {
        $whereClause = ['createdBy', '=', App::$activeUser->ID, 'AND'];
        if ( !empty( $id ) ) {
            $whereClause = array_merge( $whereClause, ['folderID', '!=', $id] );
        } else {
            $whereClause = array_merge( $whereClause, [ 'folderID', 'IS', null] );
        }
        if ( empty( $limit ) ) {
            $bookmarks = self::$db->get( $this->tableName, $whereClause );
        } else {
            $bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [ 0, $limit ] );
        }
        if ( !$bookmarks->count() ) {
            Debug::info( 'No Bookmarks found.' );
            return false;
        }
        return $this->filter( $bookmarks->results() );
    }

    public function getName( $id ) {
        $bookmarks = self::findById( $id );
        if (false == $bookmarks) {
            return 'unknown';
        }
        return $bookmarks->title;
    }

    public function getColor( $id ) {
        $bookmarks = self::findById( $id );
        if (false == $bookmarks) {
            return 'default';
        }
        return $bookmarks->color;
    }

    public function simpleByUser() {
        $whereClause = ['createdBy', '=', App::$activeUser->ID];
        $bookmarks = self::$db->get( $this->tableName, $whereClause );
        if ( !$bookmarks->count() ) {
            Debug::warn( 'Could not find any bookmarks' );
            return false;
        }
        
        $bookmarks = $bookmarks->results();
        $out = [];
        foreach ( $bookmarks as $bookmarks ) {
            $out[ $bookmarks->title ] = $bookmarks->ID;
        }
        return $out;
    }

    public function simpleObjectByUser() {
        $whereClause = ['createdBy', '=', App::$activeUser->ID];
        $bookmarks = self::$db->get( $this->tableName, $whereClause );
        if ( !$bookmarks->count() ) {
            Debug::warn( 'Could not find any bookmarks' );
            return false;
        }
        
        $bookmarks = $bookmarks->results();
        $out = [];
        foreach ( $bookmarks as $bookmarks ) {
            $obj = new \stdClass();
            $obj->title = $bookmarks->title;
            $obj->ID = $bookmarks->ID;
            $out[] = $obj;
        }
        return $out;
    }

    public function deleteByFolder( $folderID ) {
        $whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ];
        $whereClause = array_merge( $whereClause, [ 'folderID', '=', $folderID ] );
        $bookmarks = self::$db->get( $this->tableName, $whereClause );
        if ( ! $bookmarks->count() ) {
            Debug::info( 'No ' . $this->tableName . ' data found.' );
            return [];
        }
        foreach( $bookmarks->results() as $bookmark ) {
            $this->delete( $bookmark->ID );
        }
        return true;
    }

    private function resolveShortenedUrl( $url ) {
        $ch = curl_init($url);

        // Set curl options
        curl_setopt($ch, CURLOPT_NOBODY, true); // We don't need the body
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return the response
        curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Set a timeout
        // curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // Maybe sketchy?
        // Maybe sketchy?
        // curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL host verification
        // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL peer verification
        // curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);

        // added to support the regex site
        // curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);

        //  = 
        // curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'AES256+EECDH:AES256+EDH' );




        // Execute curl
        $response = curl_exec( $ch );

        // Check if there was an error
        if ( curl_errno( $ch ) ) {
            

            // Get error details
            $errorCode = curl_errno($ch);
            $errorMessage = curl_error($ch);
            // Log or display the error details
            dv('cURL Error: ' . $errorMessage . ' (Error Code: ' . $errorCode . ')');

            curl_close($ch);
            // return $url; // Return the original URL if there was an error


            $url = rtrim( $url, '/' );
            $ch2 = curl_init($url);
            curl_setopt($ch2, CURLOPT_NOBODY, true); // We don't need the body
            curl_setopt($ch2, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
            curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); // Return the response
            curl_setopt($ch2, CURLOPT_TIMEOUT, 5); // Set a timeout
            curl_exec($ch2);
            
            if ( curl_errno( $ch2 ) ) {

            }
            curl_close( $ch );
            return $url;
        }

        // Get the effective URL (the final destination after redirects)
        $finalUrl = curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL );
        curl_close( $ch );

        return $finalUrl;

        // $headers = get_headers( $url, 1 );
        $headers = @get_headers($url, 1);
        if ( $headers === false ) {
        }
        if ( isset( $headers['Location'] ) ) {
            if (is_array($headers['Location'])) {
                return end($headers['Location']);
            } else {
                return $headers['Location'];
            }
        } else {
            return $url;
        }
    }

    public function filter( $data, $params = [] ) {
        foreach ( $data as $instance ) {
            if ( !is_object( $instance ) ) {
                $instance = $data;
                $end = true;
            }
            
            $instance->iconHtml = '<i class="fa fa-fw fa-link"></i>';
                if ( ! empty( $instance->icon ) ) {
                    if ( strpos($instance->icon, 'http') !== false) {
                        $instance->iconHtml = '<img src="' . $instance->icon  .'">';
                    } else {
                        if ( ! empty( $instance->url ) ) {
                            $base_url = $this->getBaseUrl( $instance->url );
                            $instance->iconHtml = '<img src="' . $base_url . ltrim( $instance->icon, '/' ) .'">';
                        }
                    }
                }

            if ( $instance->privacy == 'private' ) {
                $instance->privacyBadge = '<span class="mx-2 translate-center badge bg-success rounded-pill">Private</span>';
                $instance->publish = '
                <a href="{ROOT_URL}bookmarks/publish/'.$instance->ID.'" class="btn btn-sm btn-outline-danger">
                    <i class="fa fa-fw fa-lock"></i>
                </a>';
            } else {
                $instance->privacyBadge = '<span class="mx-2 translate-center badge bg-danger rounded-pill">Public</span>';
                $instance->publish = '
                <a href="{ROOT_URL}bookmarks/retract/'.$instance->ID.'" class="btn btn-sm btn-outline-secondary">
                    <i class="fa fa-fw fa-lock"></i>
                </a>';
            }
            if ( empty( $instance->hiddenAt ) ) {
                $instance->hidden_class = '';
                $instance->hideBtn = '
                <a href="{ROOT_URL}bookmarks/hideBookmark/'.$instance->ID.'" class="btn btn-sm btn-outline-warning">
                    <i class="fa fa-fw fa-eye"></i>
                </a>';
            } else {
                $instance->hidden_class = 'link-hidden';
                $instance->hideBtn = '
                <a href="{ROOT_URL}bookmarks/showBookmark/'.$instance->ID.'" class="btn btn-sm btn-outline-secondary">
                    <i class="fa fa-fw fa-eye"></i>
                </a>';
            }

            if ( empty( $instance->archivedAt ) ) {
                $instance->archived_class = '';
                $instance->archiveBtn = '
                <a href="{ROOT_URL}bookmarks/archiveBookmark/'.$instance->ID.'" class="btn btn-sm btn-outline-info">
                    <i class="fa fa-fw fa-briefcase"></i>
                </a>';
            } else {
                $instance->archived_class = 'link-archived';
                $instance->archiveBtn = '
                <a href="{ROOT_URL}bookmarks/unarchiveBookmark/'.$instance->ID.'" class="btn btn-sm btn-outline-secondary">
                    <i class="fa fa-fw fa-briefcase"></i>
                </a>';
            }

            if ( ! empty( $instance->refreshedAt ) && time() < ( $instance->refreshedAt + ( 60 * 10 ) ) ) {
                $instance->refreshBtn = '
                <a href="{ROOT_URL}bookmarks/refreshBookmark/'.$instance->ID.'" class="btn btn-sm btn-danger">
                    <i class="fa fa-fw fa-refresh"></i>
                </a>';
            } else {
                $instance->refreshBtn = '
                <a href="{ROOT_URL}bookmarks/refreshBookmark/'.$instance->ID.'" class="btn btn-sm btn-success">
                    <i class="fa fa-fw fa-refresh"></i>
                </a>';
            }

            $out[] = $instance;
            if ( !empty( $end ) ) {
                $out = $out[0];
                break;
            }
        }
        return $out;
    }

    public function hide( $id ) {
        if ( !Check::id( $id ) ) {
            Debug::info( 'Bookmarks: illegal ID.' );
            return false;
        }
        $fields = [
            'hiddenAt' => time(),
        ];
        if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
            new CustomException( 'bookmarkUpdate' );
            Debug::error( "Bookmarks: $id not updated" );
            return false;
        }
        return true;
    }

    public function show( $id ) {
        if ( !Check::id( $id ) ) {
            Debug::info( 'Bookmarks: illegal ID.' );
            return false;
        }
        $fields = [
            'hiddenAt' => NULL,
        ];
        if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
            new CustomException( 'bookmarkUpdate' );
            Debug::error( "Bookmarks: $id not updated" );
            return false;
        }
        return true;
    }
    
    public function publish( $id ) {
        if ( !Check::id( $id ) ) {
            Debug::info( 'Bookmarks: illegal ID.' );
            return false;
        }
        $fields = [
            'privacy' => 'public',
        ];
        if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
            new CustomException( 'bookmarkUpdate' );
            Debug::error( "Bookmarks: $id not updated" );
            return false;
        }
        return true;
    }

    public function retract( $id ) {
        if ( !Check::id( $id ) ) {
            Debug::info( 'Bookmarks: illegal ID.' );
            return false;
        }
        $fields = [
            'privacy' => 'private',
        ];
        if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
            new CustomException( 'bookmarkUpdate' );
            Debug::error( "Bookmarks: $id not updated" );
            return false;
        }
        return true;
    }

    public function archive( $id ) {
        if ( !Check::id( $id ) ) {
            Debug::info( 'Bookmarks: illegal ID.' );
            return false;
        }
        $fields = [
            'archivedAt' => time(),
        ];
        if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
            new CustomException( 'bookmarkUpdate' );
            Debug::error( "Bookmarks: $id not updated" );
            return false;
        }
        return true;
    }

    public function unarchive( $id ) {
        if ( !Check::id( $id ) ) {
            Debug::info( 'Bookmarks: illegal ID.' );
            return false;
        }
        $fields = [
            'archivedAt' => NULL,
        ];
        if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
            new CustomException( 'bookmarkUpdate' );
            Debug::error( "Bookmarks: $id not updated" );
            return false;
        }
        return true;
    }

    public function extractMetaTags($htmlContent) {
        $doc = new \DOMDocument();
        @$doc->loadHTML($htmlContent);
        $metaTags = [];
        foreach ($doc->getElementsByTagName('meta') as $meta) {
            $name = $meta->getAttribute('name') ?: $meta->getAttribute('property');
            $content = $meta->getAttribute('content');
            if ($name && $content) {
                $metaTags[$name] = $content;
            }
        }
        return $metaTags;
    }

    public function fetchUrlData($url) {
        $ch = curl_init();

        // Set cURL options
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, true); // Include headers in the output
        curl_setopt($ch, CURLOPT_NOBODY, false); // Include the body in the output
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
        curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Set a timeout
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL host verification for testing
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL peer verification for testing

        // Execute cURL request
        $response = curl_exec($ch);

        // Check if there was an error
        if (curl_errno($ch)) {
            $errorMessage = curl_error($ch);
            curl_close($ch);
            throw new \Exception('cURL Error: ' . $errorMessage);
        }

        // Get HTTP status code
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        // Separate headers and body
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $headers = substr($response, 0, $headerSize);
        $body = substr($response, $headerSize);

        curl_close($ch);

        // Parse headers into an associative array
        $headerLines = explode("\r\n", trim($headers));
        $headerArray = [];
        foreach ($headerLines as $line) {
            $parts = explode(': ', $line, 2);
            if (count($parts) == 2) {
                $headerArray[$parts[0]] = $parts[1];
            }
        }

        return [
            'http_code' => $httpCode,
            'headers' => $headerArray,
            'body' => $body,
        ];
    }

    private function getMetaTagsAndFavicon( $url ) {
        try {
            // $url = 'https://runescape.wiki';
            $data = $this->fetchUrlData($url);
            
            // iv($data);

            // Get headers
            $headers = $data['headers'];
            iv($headers);

            // Get meta tags
            $metaTags = $this->extractMetaTags($data['body']);
        } catch (Exception $e) {
            dv( 'Error: ' . $e->getMessage());

        }
        $metaInfo = [
            'url' => $url,
            'title' => null,
            'description' => null,
            'image' => null,
            'favicon' => null
        ];
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $html = curl_exec($ch);
        curl_close($ch);

        if ($html === false) {
            return null;
        }

        $meta = @get_meta_tags( $url );

        $dom = new \DOMDocument('1.0', 'utf-8');
        $dom->strictErrorChecking = false;
        $dom->loadHTML($html, LIBXML_NOERROR);
        $xml = simplexml_import_dom($dom);
        $arr = $xml->xpath('//link[@rel="shortcut icon"]');
        
        // Get the title of the page
        $titles = $dom->getElementsByTagName('title');
        if ($titles->length > 0) {
            $metaInfo['title'] = $titles->item(0)->nodeValue;
        }
        
        // Get the meta tags
        $metaTags = $dom->getElementsByTagName('meta');
        $metadata = [];
        foreach ($metaTags as $meta) {
            $metadata[] = [
                'name' => $meta->getAttribute('name'),
                'property' => $meta->getAttribute('property'),
                'content' => $meta->getAttribute('content')
            ];
            if ($meta->getAttribute('name') === 'description') {
                $metaInfo['description'] = $meta->getAttribute('content');
            }
            if ($meta->getAttribute('itemprop') === 'image') {
                $metaInfo['google_image'] = $meta->getAttribute('content');
            }
            if ($meta->getAttribute('property') === 'og:image') {
                $metaInfo['image'] = $meta->getAttribute('content');
            }
        }
        
        // Get the link tags to find the favicon
        $linkTags = $dom->getElementsByTagName('link');
        $metadata['links'] = [];
        foreach ($linkTags as $link) {
            $metadata['links'][] = [ $link->getAttribute('rel') => $link->getAttribute('href') ];
            if ( $link->getAttribute('rel') === 'icon' || $link->getAttribute('rel') === 'shortcut icon') {
                $metaInfo['favicon'] = $link->getAttribute('href');
                break;
            }
        }

        $metaInfo['metadata'] = $metadata;
        
        return $metaInfo;
    }

    public function retrieveInfo( $url ) {
        $finalDestination = $this->resolveShortenedUrl( $url );
        $info = $this->getMetaTagsAndFavicon( $finalDestination );
        $base_url = $this->getBaseUrl( $finalDestination );

        if ( ! empty( $info['favicon'] ) ) {
            echo 'favicon exists' . PHP_EOL;
            if ( stripos( $info['favicon'], 'http' ) !== false) {
                echo 'favicon is full url' . PHP_EOL;
                $imageUrl = $info['favicon'];
            } else {
                echo 'favicon is not full url' . PHP_EOL;
                $imageUrl = trim( $base_url, '/' ) . '/' .  ltrim( $info['favicon'], '/' );
            }
            if ( $this->isValidImageUrl( $imageUrl ) ) {
                echo 'image is valid' . PHP_EOL;
                $info['favicon'] = $imageUrl;
            } else {
                echo 'image is not valid' . PHP_EOL;
                $base_info = $this->getMetaTagsAndFavicon( $base_url );
                if ( ! empty( $base_info['favicon'] ) ) {
                    echo 'parent favicon exists!';
                    if ( stripos( $base_info['favicon'], 'http' ) !== false) {
                        echo 'parent favicon is full url' . PHP_EOL;
                        $imageUrl = $base_info['favicon'];
                    } else {
                        echo 'parent favicon is not full url' . PHP_EOL;
                        $imageUrl = trim( $base_url, '/' ) . '/' .  ltrim( $base_info['favicon'], '/' );
                    }
                    if ( $this->isValidImageUrl( $imageUrl ) ) {
                        echo 'parent favicon image is valid' . PHP_EOL;
                        $info['favicon'] = $imageUrl;
                    } else {
                        echo 'parent favicon image is not valid' . PHP_EOL;
                    }
                }
            }
        } else {
            echo 'favicon does not exist' . PHP_EOL;
            $base_info = $this->getMetaTagsAndFavicon( $base_url );
            if ( ! empty( $base_info['favicon'] ) ) {
                echo 'parent favicon exists!' . PHP_EOL;
                if ( stripos( $base_info['favicon'], 'http' ) !== false) {
                    echo 'parent favicon is full url' . PHP_EOL;
                    $imageUrl = $base_info['favicon'];
                } else {
                    echo 'parent favicon is not full url' . PHP_EOL;
                    $imageUrl = trim( $base_url, '/' ) . '/' .  ltrim( $base_info['favicon'], '/' );
                }
                if ( $this->isValidImageUrl( $imageUrl ) ) {
                    echo 'parent favicon image is valid' . PHP_EOL;
                    $info['favicon'] = $imageUrl;
                } else {
                    echo 'parent favicon image is not valid' . PHP_EOL;
                }
            }
        }

        return $info;
    }

    public function refreshInfo( $id ) {
        if ( !Check::id( $id ) ) {
            Debug::info( 'Bookmarks: illegal ID.' );
            return false;
        }
        $bookmark = self::findById( $id );
        if ( $bookmark == false ) {
            Debug::info( 'Bookmarks not found.' );
            return false;
        }
        if ( $bookmark->createdBy != App::$activeUser->ID ) {
            Debug::info( 'You do not have permission to modify this bookmark.' );
            return false;
        }
        if ( time() < ( $bookmark->refreshedAt + ( 60 * 10 ) ) ) {
            Debug::info( 'You may only fetch bookmarks once every 10 minutes.' );
            return false;
        }
        
        $info = $this->retrieveInfo( $bookmark->url );

        $fields = [
            // 'refreshedAt' => time(),
        ];

        if ( empty( $bookmark->title ) && ! empty( $info['title'] ) ) {
            $fields['title'] = $info['title'];
        }
        if ( empty( $bookmark->description ) && ! empty( $info['description'] ) ) {
            $fields['description'] = $info['description'];
        }

        if ( ( empty( $bookmark->icon ) || ! $this->isValidImageUrl( $bookmark->icon ) ) && ! empty( $info['favicon'] ) ) {
            $fields['icon'] = $info['favicon'];
        }
        $fields['meta'] = json_encode( $info['metadata'] );
        
        if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
            new CustomException( 'bookmarkUpdate' );
            Debug::error( "Bookmarks: $id not updated" );
            return false;
        }
        return true;
    }

    private function getBaseUrl ( $url ) {
        if ( empty($url) ) {
            return $url;
        }

        $parsedUrl = parse_url($url);
        if (isset($parsedUrl['scheme']) && isset($parsedUrl['host'])) {
            return $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . '/';
        } else {
            return null; // URL is not valid or cannot be parsed
        }
    }

    public function isValidImageUrl($url) {
        $headers = @get_headers($url);
        if ($headers && strpos($headers[0], '200') !== false) {
            
                        return true;
            // Further check to ensure it's an image
            foreach ($headers as $header) {
                if (strpos(strtolower($header), 'content-type:') !== false) {
                    if (strpos(strtolower($header), 'image/') !== false) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}
