<?php

namespace Phlaym\Roastmonday;

use APnutI\APnutI;
use Phlaym\Roastmonday\DB\DB;
use APnutI\Exceptions\NotFoundException;

class Roastmonday extends APnutI
{
    protected static $new_avatar_keywords = ['avatar', 'image', 'picture'];
    protected DB $db;
    protected string $pics_root;
    protected string $temp_pics_root;

    public function __construct($config, $pics_root = "", $app_name = 'Roastmonday')
    {
        parent::__construct(
            $config['client_secret'],
            $config['client_id'],
            $config['scope'],
            $app_name,
            $config['redirect_url'],
            __DIR__ . '/../logs/roastmonday.log',
            'DEBUG'
        );
        $this->logger->info("App: {$this->app_name}");
        $this->pics_root = $pics_root . 'pics/';
        $this->temp_pics_root = '../temp_avatars/';
        if (!is_dir($this->pics_root . $this->temp_pics_root)) {
            mkdir($this->pics_root . $this->temp_pics_root);
        }
        // TODO: Clean up logger everywhere to use context
        // TODO: move to logrotate
        // TODO: Convert TODOs to issues
        $this->logger->info('Pics root ', ['path' => $this->pics_root]);
        $this->logger->info('Pics root realpath', ['path' => realpath($this->pics_root)]);
        $this->logger->info('Temp pics root', ['path' => $this->pics_root . $this->temp_pics_root]);
        $this->logger->info('Temp pics root realpath', ['path' => realpath($this->pics_root . $this->temp_pics_root)]);

        $db_logger = null;
        if ($this->logger instanceof \Monolog\Logger) {
            $db_logger = ($this->logger)->withName('database');
            $this->logger->info("Setting up DB Logger");
        } else {
            $this->logger->info("Logger is not a Monolog logger, can not clone for DB Logger");
        }
        $this->db = new DB(
            $config['db_servername'],
            $config['db_database'],
            $config['db_username'],
            $config['db_password'],
            logger: $db_logger
        );
    }

    // TODO: Support more extensions
    protected function getExtension($ct)
    {
        $ext = 'jpg';
        switch ($ct) {
            case 'image/png':
                $ext = 'png';
                break;
            case 'image/bmp':
                $ext = 'bmp';
                break;
            case 'image/gif':
                $ext = 'gif';
                break;
            case 'image/jpeg':
            default:
                $ext = 'jpg';
                break;
        }
        return $ext;
    }

    protected function downloadPicture($link, $target_folder = '')
    {
        $this->logger->info("Downloading picture", ['url' => $link]);
        $img_header = get_headers($link, 1);
        if (strpos($img_header[0], "200") === false && strpos($img_header[0], "OK")) {
            $this->logger->error("Error fetching avatar header: " . json_encode($img_header));
            return null;
        }
        $ext = $this->getExtension($img_header['Content-Type']);
        $exp = explode('/', explode('?', $link)[0]);
        $img_name = array_pop($exp) . '.' . $ext;

        $this->savePicture($link, $target_folder, $img_name);
        return $img_name;
    }

    protected function savePicture($url, $target_folder, $filename)
    {
        $pics_folder = $this->pics_root . $target_folder;
        $this->logger->debug("Checking pics directory", ['path' =>  $pics_folder]);
        if (!is_dir($pics_folder)) {
            mkdir($pics_folder);
        }
        $d = $pics_folder;

        if (!(substr($d, -1) === '/')) {
            $d .= '/';
        }
        $d .= $filename;
        if (!file_exists($d)) {
            $this->logger->info('Saving avatar to ', ['path' => $d]);
            $av = file_get_contents($url);
            $fp = fopen($d, "w");
            fwrite($fp, $av);
            fclose($fp);
        } else {
            $this->logger->info('already exists. Skipping.', ['path' => $d]);
        }
    }

    protected function getThemeMondaysFromWiki()
    {
        $m = [];
        $html = file_get_contents('https://wiki.pnut.io/ThemeMonday');
        if ($html === false) {
            $this->logger->error('Error connecting to pnut wiki');
            return [];
        }
        // TODO: Use \Dom\HTMLDocument
        $doc = new \DOMDocument();
        $doc->loadHTML($html);
        if ($doc === false) {
            $this->logger->error('Error loading HTML from pnut wiki');
            return [];
        }
        $past_themes_element = $doc->getElementById('Past_Themes_on_Pnut');
        $past_themes_list = [];
        try {
            $past_themes_list = $past_themes_element->parentNode->nextSibling->nextSibling;
        } catch (\Exception $e) {
            $this->logger->error('Error parsing wiki: ' . $e->getMessage());
            return [];
        }
        foreach ($past_themes_list->childNodes as $child) {
            if ($child->nodeName === 'li') {
                $tag = null;
                $date = null;
                foreach ($child->childNodes as $list_entry) {
                    if ($list_entry->nodeName === 'a' && $list_entry->nodeValue !== null) {
                        $date = \DateTime::createFromFormat('Y F d', $list_entry->nodeValue);
                        if ($date === false) {
                            $date = null;
                        }
                    } elseif ($list_entry->nodeName === '#text' && $list_entry->nodeValue !== null) {
                        $arr = explode('#', str_replace(': ', '', $list_entry->nodeValue));
                        if (count($arr) > 1) {
                            $tag = '#' . $arr[1];
                        }
                    }
                    if ($tag !== null && $date !== null) {
                        $this->logger->info(
                            'Found ThemeMonday in wiki: '
                                . trim($tag)
                                . ' on '
                                . $date->format('Y-m-d')
                        );
                        $m[] = [
                            'date' => new \DateTime($date->format('Y-m-d')),
                            'tag' => trim($tag)
                        ];
                    }
                }
            }
        }
        return $m;
    }

    public function getThemeMonday($id)
    {
        $t = $this->db->getTheme($id);
        $this->logger->debug('Theme ' . $id . ': ' . json_encode($t));
        return $t;
    }

    protected function getThemeMondaysFromAccountPost()
    {
        $this->logger->info('Parsing @ThemeMonday polls');
        $m = [];
        $p = [];
        try {
            $p = $this->getPollsFromUser(616);
        } catch (\Exception $e) {
            $this->logger->error('Error reading @ThemeMonday polls: ' . $e->getMessage());
            return [];
        }

        $p = array_filter($p, function ($e) {
            return stripos($e->prompt, '#thememonday') !== false;
        });
        $this->logger->info('Found ' . count($p) . ' polls');
        if (count($p) === 0) {
            return [];
        }
        foreach ($p as $poll) {
            $tag = $poll->getMostVotedOption();
            if (count($tag) !== 1) {
                $this->logger->info('Skipping ' . implode(', ', $tag) . ', because it was a tie');
                continue;
            }
            $date = Roastmonday::getMondayAfterPoll($poll);
            $this->logger->info(
                'Found ThemeMonday: '
                    . $tag[0]->text
                    . ' on '
                    . $date->format('Y-m-d')
            );
            $m[] = [
                'date' => new \DateTime($date->format('Y-m-d')),
                'tag' => $tag[0]->text
            ];
        }
        return $m;
    }

    protected static function getMondayAfterPoll($poll)
    {
        $d = $poll->closed_at;
        $days_to_add = (7 - ($d->format('w') - 1)) % 7;
        return $d->modify('+' . $days_to_add . ' days');
    }

    protected function savePictureForPost($post, $theme_id)
    {
        $this->logger->info('Checking post ' . $post->id . ' for theme ' . $theme_id);
        if (!empty($post->user) && !empty($post->user->avatar_image)) {
            $this->logger->info(
                'Found new avatar on post '
                    . $post->id
                    . ' by @'
                    . $post->user->username
            );
            $filename = $this->downloadPicture($post->user->avatar_image->link, $theme_id);
            if ($filename !== null) {
                $realname = null;
                if (isset($post->user, $post->user->name)) {
                    $realname = $post->user->name;
                }
                $text = "";
                if (isset($post->content)) {
                    if (isset($post->content->html)) {
                        $text = $post->content->html;
                    } elseif (isset($post->content->text)) {
                        $text = $post->content->text;
                    }
                }
                $this->logger->info(
                    'Saving avatar by '
                        . $post->user->username
                        . ' for '
                        . $theme_id
                        . ' to database'
                );
                try {
                    $status = $this->db->saveAvatar(
                        $theme_id,
                        $post->user->username,
                        $realname,
                        $post->id,
                        $text,
                        $post->created_at->getTimestamp(),
                        $filename
                    );
                    switch ($status) {
                        case DB::$TRIED_INSERT_DUPLICATE:
                            $this->logger->info('Error, tried to insert a duplicate avatar');
                            break;
                        case DB::$SKIPPED_DUPLICATE:
                            $this->logger->info('Skipped, duplicate avatar');
                            break;
                        case DB::$SUCCESS:
                            $this->logger->info('Successfully inserted avatar');
                            break;
                        default:
                            $this->logger->info('Unknown return code ' . $status);
                            break;
                    }
                } catch (\Exception $e) {
                    $this->logger->error('Error inserting avatar into database: ' . $e->getMessage());
                }
                return $filename;
            }
            $this->logger->warning(
                "Cannot save picture for post {$post->id}: Doesn't have a user or user doesn't have an avatar"
            );
        }
    }

    public function getPost($post_id, $args = [])
    {
        $post = parent::getPost($post_id, $args);
        if (array_key_exists('avatarWidth', $args)) {
            $a = parent::getAvatar($post->user->id, ['w' => $args['avatarWidth']]);
            $this->logger->debug("Resized avatar: {$a}");
            $post->user->avatar_image->link = $a;
        }
        return $post;
    }

    protected function removeDuplicateThemes($arr)
    {
        $tmp = [];
        foreach ($arr as $e) {
            if (!in_array($e, $tmp)) {
                $f = false;
                foreach ($tmp as $t) {
                    if (strtolower($t['tag']) === strtolower($e['tag'])) {
                        $f = true;
                        break;
                    }
                }
                if (!$f) {
                    $tmp[] = $e;
                }
            }
        }
        return $tmp;
    }

    public function getThemeMondays()
    {
        $themes = $this->db->listThemes();
        $this->logger->debug('Themes: ' . json_encode($themes));
        return $themes;
    }

    public function getPicturesForTheme($id)
    {
        $this->logger->debug("Avatars for theme {$id}: ");
        $pics_folder = $this->pics_root . $id . '/';
        $pics = [];
        if (!is_dir($pics_folder)) {
            $this->logger->warning($pics_folder . ' ist not a directory');
            return [];
        }
        $this->logger->debug("DB: " . json_encode($this->db));
        $avatars = $this->db->getAvatarsForTheme($id);
        foreach ($avatars as $avatar) {
            $avatar['file'] = $pics_folder . $avatar['file'];
            $pics[] = $avatar;
        }
        return $pics;
    }

    public function findThemeMondays()
    {
        $this->logger->info('Searching for theme mondays');
        $m = $this->getThemeMondaysFromAccountPost();
        $m = array_filter($m, function ($e) {
            return !empty($e);
        });
        $m = $this->removeDuplicateThemes(
            array_merge($m, $this->getThemeMondaysFromWiki())
        );
        usort($m, function ($a, $b) {
            if ($a['date'] == $b['date']) {
                return 0;
            }
            return $a['date'] > $b['date'] ? -1 : 1;
        });
        $this->logger->info('Found theme mondays', $m);
        return $m;
    }

    public function savePicturesForTheme($theme)
    {
        $tag = preg_replace('/^#/', '', $theme['tag']);
        $tag = preg_replace('/ .*$/', '', $tag);
        $posts = [];
        $this->logger->info('Searching pictures for: ' . $tag);
        foreach (Roastmonday::$new_avatar_keywords as $keyword) {
            $query = [
                'tags' => $tag,
                'q' => $keyword,
                'include_deleted' => false,
                'include_client' => false,
                'include_counts' => false,
                'include_html' => false,
            ];
            $p = $this->searchPosts($query);
            $this->logger->info('Found ' . count($p) . ' posts');
            foreach ($p as $post) {
                $this->savePictureForPost($post, $theme['id']);
                if (!in_array($post, $posts)) {
                    $posts[] = $post;
                }
            }
        }
    }

    public function addTheme($tag, $date)
    {
        $tag = preg_replace('/^#/', '', $tag);
        $tag = preg_replace('/ .*$/', '', $tag);
        $this->logger->info('Adding: ' . $tag . ' to database');
        $id = $this->db->addTheme($tag, $date->format('Y-m-d'));
        if ($id !== -1) {
            $pics_folder = $this->pics_root . $id . '/';
            if (!is_dir($pics_folder)) {
                mkdir($pics_folder);
            }
        }
        return $id;
    }

    public function addAvatar(
        $theme,
        $should_post,
        $avatar,
        $avatar_duration,
        $posttext = null,
        $overwrite_if_exist = false
    ) {
        $this->logger->info("Adding temporary avatar to theme {$theme} for {$avatar_duration} days");

        switch ($avatar['error']) {
            case UPLOAD_ERR_OK:
                # ok
                break;
            case UPLOAD_ERR_NO_FILE:
                return ['error' => ['message' => 'No avatar has been uploaded']];
            case UPLOAD_ERR_FORM_SIZE:
                return ['error' => ['message' => 'The uploaded avatar\'s file size is too big']];
            default:
                return ['error' => ['message' => 'An unknown error has occured while uploading the avatar']];
        }

        #1. Save current user avatar
        $a = self::getAvatar('me');
        $this->logger->info("Current avatar: {$a}");
        #$a = explode('/', $a);
        #$filename = explode('?', end($a))[0];
        $original_avatar = $this->downloadPicture($a, $this->temp_pics_root);
        if (empty($original_avatar)) {
            return ['error' => ['message' => 'Could not download original avatar']];
        }

        #2. Save current avatar filename + enddate to DB
        $current_user = null;
        try {
            $current_user = $this->getAuthorizedUser();
            if (empty($current_user)) {
                return ['error' => ['message' => 'Could not fetch the authorized user']];
            }
            $this->logger->info("Current user: @{$current_user->username} ({$current_user->id})");
            $avatar_duration = min($avatar_duration, 1);
            $d = new \DateTime();
            $d->add(new \DateInterval("P{$avatar_duration}D"));
            $this->db->addTempAvatar($current_user->id, $d->getTimestamp(), $original_avatar, $this->access_token);
        } catch (\Exception $e) {
            return ['error' => ['message' => 'Could not save original avatar: ' . $e->getMessage()]];
        }

        #3. Upload new avatar
        $astr = print_r($avatar, true);
        $this->logger->info("Avatar: {$astr}");
        try {
            $current_user = $this->updateAvatarFromUploaded($avatar);
        } catch (\Exception $e) {
            return ['error' => ['message' => 'Could not update to the theme avatar: ' . $e->getMessage()]];
        }
        #4. Write post?
        $this->logger->info('Post about theme avatar? ' . ($should_post ? 'Yes' : 'No') . ': ' . $should_post);
        if (!$should_post) {
            try {
                $this->logger->info('Adding post-less avatar to gallery');
                $this->authenticateServerToken();
                $response = $this->manuallyAddAvatar(
                    $theme,
                    0,
                    $avatar,
                    $current_user,
                    overwrite_if_exist: $overwrite_if_exist
                );
                if (!empty($response['error'])) {
                    $this->logger->warning('Error adding to gallery');
                    $rs = print_r($response, true);
                    $this->logger->warning($rs);
                    return [
                        'success' => true,
                        'warn' => 'Your avatar has been updated, but an error occured adding it to the gallery: '
                            . $response['error']['message']
                    ];
                } else {
                    $this->logger->info('Successfully added to gallery');
                }
            } catch (\Exception $e) {
                $this->logger->error($e->getMessage());
                $this->logger->error($e->getTraceAsString());
                return [
                    'success' => true,
                    'warn' => 'Your avatar has been updated, but an error occured adding it to the gallery: '
                        . $e->getMessage()
                ];
            }
            return ['success' => true];
        }
        if (empty($posttext)) {
            return [
                'success' => true,
                'warn' => 'You selected to post about new avatar,'
                    . '<br />but your posttext is empty.<br />'
                    . 'Your avatar has been updated, but no post has been created'
            ];
        }
        $this->logger->info('Posting about theme avatar');
        $post = null;
        try {
            // TODO: Add photo as attachment to post as well
            $post = $this->createPost($posttext, is_nsfw: false, auto_crop: true);
        } catch (\Exception $e) {
            $this->logger->error($e->getMessage());
            $this->logger->error($e->getTraceAsString());
            return [
                'success' => true,
                'warn' => 'Your avatar has been updated, but an error occured creating the post:<br />'
                    . $e->getMessage()
            ];
        }
        $this->logger->info('Post created successfully');

        #5. Add to theme DB
        try {
            $this->authenticateServerToken();
            $response = $this->manuallyAddAvatar($theme, $post->id, null, overwrite_if_exist: $overwrite_if_exist);
            $this->logger->info('Successfully added to gallery');
        } catch (\Exception $e) {
            $this->logger->error($e->getMessage());
            $this->logger->error($e->getTraceAsString());
            return [
                'success' => true,
                'postID' => $post->id,
                'warn' => 'Your avatar has been updated, and your post created,'
                    . '<br />but an error occured adding it to the gallery: '
                    . $e->getMessage()
            ];
        }
        return ['success' => true, 'postID' => $post->id, 'newGalleryAvatar' => $response];
    }

    public function manuallyAddAvatar($theme, $post_id, $avatar, $user = null, $overwrite_if_exist = false)
    {
        $this->logger->info(
            "Manually adding avatar to theme from post",
            ['theme' => $theme, 'post_id' => $post_id]
        );
        $resp = [];
        $date = new \DateTime();
        $post = null;
        try {
            if (empty($user)) {
                $post = $this->getPost($post_id);
                $user = $post->user;
                $date = $post->created_at;
                if (empty($user)) {
                    $this->logger->error("Error fetching avatar from post #{$post_id}. Post has no creator.");
                    $resp['error'] = [
                        'code' => static::$ERROR_FETCHING_CREATOR_FIELD,
                        'message' => 'Error fetching avatar. Post has no creator.'
                    ];
                    return $resp;
                }
            }
            if (empty($user->avatar_image) || empty($user->avatar_image->link)) {
                $this->logger->error("Error fetching avatar from post #{$post_id}. Post creator has no avatar.");
                $resp['error'] = [
                    'code' => static::$ERROR_FETCHING_CREATOR_FIELD,
                    'message' => 'Error fetching avatar. Post creator has no avatar.'
                ];
                return $resp;
            }
            $astr = print_r($avatar, true);
            $this->logger->debug("Manually adding avatar: {$astr}");
            if (!empty($avatar) && (empty($avatar['error']) || $avatar['error'] === 0)) {
                $this->logger->info("Manually added post #{$post_id} has an avatar attached.");
                $ext = $this->getExtension($avatar['type']);
                $target_file_name_without_theme = time() . '_' . $post_id . '.' . $ext;
                $target_file_name = $theme . '/' . $target_file_name_without_theme;
                $target_file = $this->pics_root . $target_file_name;
                if (move_uploaded_file($avatar["tmp_name"], $target_file)) {
                    // TODO: Remove hardcoded path
                    $resp['img'] = 'pics/' . $target_file_name;
                } else {
                    $this->logger->error("Error saving uploaded avatar from post #{$post_id}. Post has no creator.");
                    $resp['error'] = [
                        'code' => static::$ERROR_UNKNOWN_SERVER_ERROR,
                        'message' => 'Uploaded file could not be moved'
                    ];
                    return $resp;
                }
                $this->logger->info("Saved manually uploaded file to " . $target_file . ". Adding to database");
                $realname = null;
                $username = '';
                if (isset($user)) {
                    if (isset($user->name)) {
                        $realname = $user->name;
                    }
                    $username = $user->username;
                    $resp['presence'] = $user->getPresenceInt();
                }
                $posttext = empty($post) ? '' : $post->getText();
                $status = $this->db->saveAvatar(
                    $theme,
                    $username,
                    $realname,
                    $post_id,
                    $posttext,
                    $date->getTimestamp(),
                    $target_file_name_without_theme,
                    $overwrite_if_exist
                );
                $this->logger->info(
                    "Saved manually uploaded file and added to database",
                    ['path' => $target_file, 'db_status_code' => $status, 'db_status' => DB::getResultName($status)]
                );
            } else {
                $this->logger->info(
                    "Manually added post #{$post_id} doesn't have an avatar attached. fetching from  user object."
                );
                $filename = $this->savePictureForPost($post, $theme);
                if (empty($filename)) {
                    $this->logger->error("Error fetching avatar from post #{$post_id}.");
                    $resp['error'] = [
                        'code' => static::$ERROR_UNKNOWN_SERVER_ERROR,
                        'message' => "Error fetching avatar from post #{$post_id}."
                    ];
                    return $resp;
                }
                $resp['img'] = 'pics/' . $theme . '/' . $filename;
                //protected function savePicture($avatar, $theme_id, $filename) {
                //$avatar = file_get_contents($this->pics_root.$theme.'/'.$filename);
                $this->logger->info("Saved downloaded avatar to {$resp['img']}");
            }
            $resp['presence'] = -1;
            $realname = null;
            $username = '';
            if (isset($user)) {
                if (isset($user->name)) {
                    $realname = $user->name;
                }
                $username = $user->username;
                $resp['presence'] = $user->getPresenceInt();
            }
            $resp['user'] = '@' . $username;

            $text = "";
            /*
            if (isset($post->content)) {
                if (isset($post->content->html)) {
                    $text = $post->content->html;
                } elseif (isset($post->content->text)) {
                    $text = $post->content->text;
                }
            }
            */
            $text = empty($post) ? '' : $post->getText();
            $resp['success'] = true;
            $resp['realname'] = $realname;
            $resp['posttext'] = $text;
            $resp['postid'] = $post_id;
            $resp['timestamp'] = $date->getTimestamp();
            $this->logger->info(
                "Successfully added manually uploaded avatar for user to theme",
                ['user' => $resp['user'], 'theme' => $theme]
            );
            return $resp;
        } catch (NotFoundException $nfe) {
            $this->logger->error(
                "Error adding manually uploaded avatar for theme. Post could not be found",
                ['post_id' => $post_id, 'theme' => $theme, 'exception' => $nfe]
            );
            $resp['error'] = ['code' => static::$ERROR_NOT_FOUND, 'message' => 'Post could not be found'];
            return $resp;
        } catch (\Exception $e) {
            $this->logger->error("Error adding manually uploaded avatar for theme {$theme}: " . $e->getMessage());
            $resp['error'] = ['code' => static::$ERROR_UNKNOWN_SERVER_ERROR, 'message' => $e->getMessage()];
            return $resp;
        }
    }

    public function resetOriginalAvatars()
    {
        $this->logger->info('Get outdated avatars');
        $to_reset = $this->db->getOutdatedThemeAvatars();
        $this->logger->info('Found ' . count($to_reset) . ' outdated avatars');
        foreach ($to_reset as $value) {
            $user_id = $value['user_id'];
            $p = $this->pics_root . $this->temp_pics_root . $value['original_avatar'];
            $avatar_path = realpath($p);
            if ($avatar_path === false) {
                $this->logger->error("Avatar path '{$p}' for user {$user_id} does not exist!");
            }
            $this->logger->info('Resetting avatar for user ' . $user_id . ' to original at: ' . $avatar_path);
            $this->access_token = $value['auth_token'];
            try {
                $this->updateAvatar($avatar_path);
            } catch (\Exception $e) {
                $this->logger->error('Error resetting user avatar: ' . $e->getMessage());
                $this->logger->error($e->getTraceAsString());
                return;
            }
            $this->logger->info('Resetted avatar for user ' . $user_id . ' to original at: ' . $avatar_path);
            $this->logger->info('Deleting avatar for user ' . $user_id . ' at: ' . $avatar_path);
            $res = unlink($avatar_path);
            if (!$res) {
                $this->logger->error('Failed to delete avatar at: ' . $avatar_path);
                return;
            }
            $this->logger->info('Deleted avatar for user ' . $user_id . ' at: ' . $avatar_path);
            try {
                $this->db->removeOutdatedThemeAvatars($user_id);
            } catch (\Exception $e) {
                $this->logger->error('Error resetting user avatar', ['exception' => $e]);
                return;
            }
            $this->logger->info('Avatar for user ' . $user_id . ' is back to its original value!');
        }
    }
}
