<?php

/*
 *
 * コンバート処理（PPP Ver.1系統からVer.2系統へ変換）
 * 
 * 
 */

namespace pictpostpersonal;

use Exception;
use pictpostpersonal\controller\page\ThemePage;
use pictpostpersonal\Logger;
use pictpostpersonal\SQLiteCRUD;
use pictpostpersonal\model\Activity;
use pictpostpersonal\model\ThemeSetting;

require_once 'autoload.php';

if (defined('pictpostpersonal\PPP_PATH') === false) {
    exit();
}

if (isset($_POST['username'], $_POST['password'])) {

    //PPP1存在チェック
    if (!file_exists('../env/user_setting.php')) {
        echo ("PPP1環境が見つかりません。PPP1環境内にサブディレクトリを作成し、そこに本ファイルを配置してください。\r\n");
        exit();
    }

    //ディレクトリ作成とパーミッション設定
    createDirectory('./env', true);
    createDirectory('./themes', true);
    createDirectory('./env/templates', true);
    createDirectory('./env/systemlog', true);
    createDirectory('./env/template_cache', true);
    createDirectory('./user_resource', false, true);
    createDirectory('./user_resource/thumbnails');
    createDirectory('./images', false, true);

    //PPP1設定のコピー
    copyFolderContents('../env', './env');

    //最近の画像100件をコピー
    copyImages('../images', './images', 999);

    //システム用画像群のコピー
    copyFile('../images/ogimage.jpg', './images/ogimage.jpg');
    copyFile('../images/banner.jpg', './images/banner.jpg');
    copyFile('../images/usericon.jpg', './images/usericon.jpg');
    copyFile('../images/userfavicon.png', './images/userfavicon.png');
    copyFile('../images/mini_banner.png', './images/mini_banner.png');


    //システム設定ファイルの存在チェック
    if (!file_exists(SYSTEM_PATH)) {
        echo ("初期化されていないため初期設定を行ってください\r\n");
        exit();
    }

    //ログインチェック
    require_once(USER_PATH);
    if (!isset($sys_username)) {
        $sys_username = SYS_USERNAME;
    }
    if (!isset($sys_password)) {
        $sys_password = SYS_PASSWORD;
    }
    $user = filter_input(INPUT_POST, 'username');
    $password = filter_input(INPUT_POST, 'password');
    if ($user != $sys_username || !password_verify($password, $sys_password)) {
        echo ("ユーザーIDまたはパスワードが不正です\r\n");
        return;
    }

    //ログ記録準備
    $now = date('Y-m-d-H-i-s');
    $log = new Logger("./updatelog_$now.txt", Logger::LOG_LEVEL_ALL);
    $log->logWithShow("データコンバートを開始します\r\n");

    //システムDBへ接続開始
    require_once(SYSTEM_PATH);
    if (!file_exists($system_db_file)) {
        $log->logWithShow("システムファイルに障害が発生しています。初期化してください。\r\n");
        exit;
    }
    try {
        $db = new \PDO("sqlite:$system_db_file");
        $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        $db->beginTransaction();
    } catch (Exception $ex) {
        $log->logWithShow("システムファイルに障害が発生しています。初期化してください。\r\n");
        exit;
    }

    //ユーザー設定更新
    updateUserSetting();

    //settingsテーブルが存在している場合アップデート済みと判断する
    if (tableExists($db, 'settings')) {
        $log->logWithShow('すでにコンバート実施済みです');
        exit;
    }

    try {
        copy('../env/user.css', './user_resource/user.css');

        //DBバックアップを実施
        $log->logWithShow("DBバックアップ $system_db_file to {$system_db_file}.{$now}.db\r\n");
        copy($system_db_file, "{$system_db_file}.{$now}.db");

        //テーブル構造更新
        $db->exec('ALTER TABLE images RENAME TO media');
        $db->exec('ALTER TABLE comments RENAME TO messages');

        $db->exec("ALTER TABLE accounts ADD COLUMN type ;");
        $db->exec("ALTER TABLE media ADD COLUMN created_at DATETIME;");
        $db->exec("ALTER TABLE media ADD COLUMN sub_dir TEXT;");
        $db->exec("ALTER TABLE articles ADD COLUMN type INTEGER;");
        $db->exec("ALTER TABLE articles ADD COLUMN status TEXT;");
        $db->exec("ALTER TABLE articles ADD COLUMN hidden_at_list BOOL;");
        $db->exec("ALTER TABLE articles ADD COLUMN contents TEXT;");
        $db->exec("ALTER TABLE articles ADD COLUMN thumbnail_type INTEGER;");
        $db->exec("ALTER TABLE messages ADD COLUMN message TEXT;");

        createSysDate($db, 'accounts');
        createSysDate($db, 'activity');
        createSysDate($db, 'media');
        createSysDate($db, 'articles');
        $db->exec("update articles set sys_created_at = created_at, sys_updated_at = updated_at,  status=0, hidden_at_list = hidden;");
        $db->exec("update articles set type = 0 where style <> 99;");
        $db->exec("update articles set type = 1 where style = 99;");
        $db->exec("update articles set thumbnail_type = 0 where thumbnail = '' or thumbnail is null;");
        $db->exec("update articles set thumbnail_type = 3 where thumbnail <> '' and thumbnail is not null;");
        $db->exec("update articles set thumbnail_type = 1 where thumbnail = '@user_thumbnail';");
        $db->exec("update articles set thumbnail_type = 2 where thumbnail = '@all';");
        $db->exec("update articles set thumbnail_type = 4 where thumbnail = '@text';");
        $db->exec("update articles set contents = comment where contents is null;");
        $db->exec("update messages set message = comment;");
        $db->commit(); //一旦コミット

        $sql_crud = new SQLiteCRUD($db);

        //各モデルの初期化
        model\Account::initialize($sql_crud);
        model\Media::initialize($sql_crud);
        model\Article::initialize($sql_crud);
        model\Message::initialize($sql_crud);
        model\Setting::initialize($sql_crud);
        model\Activity::initialize($sql_crud);
        model\SystemActivity::initialize($sql_crud);
        model\Template::initialize($sql_crud);
        model\ThemeSetting::initialize($sql_crud);



        $db->beginTransaction();

        $log->logWithShow("設定値のアップデート開始\r\n");
        convertSettingToSettings($db); //設定値のアップデート


        $log->logWithShow("Imageテーブルのアップデート開始\r\n");
        convertImagesToMedia($db); //imagesテーブルのアップデート

        $log->logWithShow("Accountsテーブルのアップデート開始\r\n");
        updateAccounts($db);

        $log->logWithShow("Articlesテーブルのアップデート開始\r\n");
        updateArticles($db, $log);

        $db->commit();

        $log->logWithShow("変換完了\r\n");
        echo ("<a href=\"./setting\" target=\"_blank\">設定画面</a>にアクセスし、一度設定を保存してください。\r\n");
    } catch (Exception $ex) {
        $db->rollBack();
        $log->logWithShow("エラーが発生しました\r\n");
        $log->logWithShow($ex->getMessage());
        exit();
    }


    exit();
}

function createTempDB($db_file_path)
{
    //テンポラリ用DB
    $tmp_db = new \PDO("sqlite:tmp_$db_file_path");
    $tmp_db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
    $retry = 0;
    while ($retry < 10) {
        try {
            $tmp_db->exec('PRAGMA journal_mode=wal');
            $retry = 99;
        } catch (Exception $ex) {
            sleep(1000);
            $retry++;
        }
    }
    if ($retry != 99) {
        echo '失敗';
        exit;
    }
}

//ログインユーザー情報ファイルの更新
function updateUserSetting()
{
    require './env/user_setting.php';
    if (isset($sys_username, $sys_password)) {
        //パスワード設定
        $fp = fopen('./env/user_setting.php', 'w');
        fwrite($fp, "<?php\n");
        fwrite($fp, 'const SYS_USERNAME="' .  preg_replace('/[\'"$]/', '\\\\$0', $sys_username) . '";' . "\r\n");
        fwrite($fp, 'const SYS_PASSWORD="' .   preg_replace('/[\'"$]/', '\\\\$0', $sys_password) . '";' . "\r\n");
        fwrite($fp, 'const LOCK_MODE=false;' . "\r\n");
        fclose($fp);
    }
}

function copyImages($sourceDir, $destDir, $limit = 100)
{
    // ディレクトリ存在チェック
    if (!is_dir($sourceDir) || !is_dir($destDir)) {
        throw new \Exception('ディレクトリが存在しません');
    }

    // ファイル一覧取得（. や .. を除外）
    $files = array_filter(
        scandir($sourceDir),
        fn($file) => is_file($sourceDir . DIRECTORY_SEPARATOR . $file)
    );

    // 更新日時で降順ソート
    usort($files, function ($a, $b) use ($sourceDir) {
        return filemtime($sourceDir . DIRECTORY_SEPARATOR . $b)
            <=> filemtime($sourceDir . DIRECTORY_SEPARATOR . $a);
    });

    // 上位100件をコピー
    $filesToCopy = array_slice($files, 0, $limit);

    foreach ($filesToCopy as $file) {
        $src  = $sourceDir . DIRECTORY_SEPARATOR . $file;
        $dest = $destDir   . DIRECTORY_SEPARATOR . $file;

        if (!copy($src, $dest)) {
            error_log("コピー失敗: {$file}");
        }
    }
}

//ファイルをコピーする。ただし上書きはしない
function copyFile($src, $dest)
{
    if (file_exists($src) && !file_exists($dest)) {
        copy($src, $dest);
    }
}

//ディレクトリ作成
function createDirectory($dir, $make_deny_htaccess = false, $make_deny_php_exec_htaccess = false)
{
    if (!file_exists($dir)) {
        mkdir($dir, 0755);
        $fp = fopen("{$dir}/index.html", 'w');
        fclose($fp);
    }

    //直接アクセスを拒否するhtaccessの設置
    if ($make_deny_htaccess) {
        if (!file_exists("{$dir}/.htaccess")) {
            $fp = fopen("{$dir}/.htaccess", 'w');
            fwrite($fp, "Require all denied\n");
            fclose($fp);
            chmod("{$dir}/.htaccess", 0604);
        }
    }
    if ($make_deny_php_exec_htaccess) {
        if (!file_exists("{$dir}/.htaccess")) {
            $fp = fopen("{$dir}/.htaccess", 'w');
            fwrite($fp, "<FilesMatch \"\.php|cgi|pl$\">\nRequire all denied\n</FilesMatch>");
            fclose($fp);
            chmod("{$dir}/.htaccess", 0604);
        }
    }
}

//ディレクトリパーミッション変更
function permissionChange(string $dir, $permission = 0755)
{
    if (file_exists($dir)) {
        chmod($dir, $permission);
    }
}

//フォルダ全体をコピーする
function copyFolderContents($source, $destination)
{
    // ディレクトリを作成（存在しない場合）
    if (!is_dir($destination)) {
        mkdir($destination, 0755, true);
    }

    // ファイルおよびディレクトリの一覧を取得
    $items = scandir($source);
    $items = array_diff($items, array('.', '..'));

    // 各アイテムをコピー
    foreach ($items as $item) {
        $source_path = $source . '/' . $item;
        $destination_path = $destination . '/' . $item;

        if (is_dir($source_path)) {
            // ディレクトリの場合、再帰的に中身をコピー
            copyFolderContents($source_path, $destination_path);
        } else {
            // ファイルの場合、コピー
            copy($source_path, $destination_path);
        }
    }
}

//テーブル存在チェック
function tableExists(\PDO $db, string $table_name): bool
{
    // SQLiteのメタデータテーブルを照会して、テーブルの存在をチェック
    $stmt = $db->prepare('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=:tableName');
    $stmt->bindParam(':tableName', $table_name, \PDO::PARAM_STR);
    $stmt->execute();

    // 結果が返ってきたかどうかをチェック
    return $stmt->fetch() !== false;
}

function createSysDate(\PDO $db, string $table_name)
{
    echo $table_name . "<br>";
    // フィールド定義の作成
    $sql = "ALTER TABLE {$table_name} ADD COLUMN sys_created_at TEXT;";
    $sql .= "ALTER TABLE {$table_name} ADD COLUMN sys_updated_at TEXT;";
    $db->exec($sql);

    $escaped_trigger_name = 'trigger_' . $table_name . '_sys_updated_at';
    $sql = "CREATE TRIGGER $escaped_trigger_name AFTER UPDATE ON {$table_name} 
                       BEGIN 
                           UPDATE {$table_name} SET sys_updated_at = DATETIME('now', 'localtime') 
                           WHERE rowid = NEW.rowid; 
                       END;";
    $db->exec($sql);

    $escaped_trigger_name = 'trigger_' . $table_name . '_sys_created_at';
    $sql = "CREATE TRIGGER $escaped_trigger_name AFTER INSERT ON {$table_name} 
                       BEGIN 
                           UPDATE {$table_name} SET sys_created_at = DATETIME('now', 'localtime') 
                           WHERE rowid = NEW.rowid; 
                       END;";
    $db->exec($sql);
}

//設定値のアップデート
function convertSettingToSettings($db)
{
    //settingsテーブル作成
    $query = '
            CREATE TABLE IF NOT EXISTS settings (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                key string ,
                value string
            )
        ';
    $db->query($query);

    $query = 'SELECT * FROM setting LIMIT 1';
    $stmt = $db->query($query);
    $row = $stmt->fetch(\PDO::FETCH_ASSOC);
    if ($row) {
        foreach ($row as $key => $value) {
            $insert_query = 'INSERT INTO settings (Key, Value) VALUES (:key, :value)';
            $insert_stmt = $db->prepare($insert_query);
            if ($value == 'on') {
                $value = true;
            } else if ($value == 'off') {
                $value = false;
            }
            $insert_stmt->execute([':key' => $key, ':value' => $value]);
        }
    }

    //設定項目名の変更
    $sql = 'Update Settings Set key=\'use_nsfw\' where key = \'nsfw\';';
    $db->exec($sql);

    $sqlc = new SQLiteCRUD($db);
    $sqlc->table('articles')->create([
        'created_at' => '1980-01-01',
        'contents' => $row['instruction_text'] ?? '',
        'hidden' => 1,
        'style' => '99',
        'type' => 2,
    ]);
    $sqlc->table('articles')->create([
        'created_at' => '1980-01-01',
        'contents' => $row['nsfw_text'] ?? '',
        'hidden' => 1,
        'style' => '99',
        'type' => 3,
    ]);
    $db->exec("insert into settings (key,value) values ( 'banner', 'banner.jpg');");
    $db->exec("insert into settings (key,value)  values('mini_mabber', 'mini_banner.jpg');");
    $db->exec("insert into settings (key,value) values('icon','usericon.jpg' );");
}

//Imagesテーブルのアップデート・Mediaに変更
function convertImagesToMedia(\PDO $db)
{
    //created_atにとりあえず記事の作成日を設定
    $sql = '
        UPDATE media
        SET created_at = (
            SELECT articles.created_at
            FROM articles
            WHERE articles.id = media.article_id
        )
    ';
    $db->exec($sql);
}

//pixivのリンクアイコンを更新
function updateAccounts(\PDO $db)
{
    $sql = '
        UPDATE accounts
        SET icon = \'fa-brands fa-pixiv\'
        where icon = \'pixiv\'
    ';
    $db->exec($sql);
}


//記事内容の更新
function updateArticles(\PDO $db, Logger $log)
{
    $sql = new SQLiteCRUD($db);

    $rows = $sql->rawQuery('select * from articles');
    foreach ($rows as $index => $row) {
        $status = '';
        //Markdownの書式修正 #の後には半角スペースが必要
        if (($row['contents'] ?? '') && strstr($row['contents'], '#')) {
            $s = $row['contents'];
            $s = preg_replace('/^(#+)([^# ])/i', '$1 $2', $s);
            if ($s != $row['contents']) {
                $row['contents'] = $s;
                $sql->table('articles')->where("id=:id", ['id' => $row['id']])->update($row);
                $status .= 'Markdown #修正';
            }
        }
        if (strpos($row['contents'] ?? '', 'plugin_') > 0) {
            $log->logWithShow("{$row['id']} - {$row['created_at']} - {$row['title']} / Plugin部分の更新が必要な可能性があります\r\n");
        }
        if ($status != '') {
            $log->logWithShow("{$row['id']} - {$row['created_at']} - {$row['title']} を更新しました({$status})\r\n");
        }
    }
}


?>


<!doctype html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <title>コンバート処理</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="theme-color" content="#fafafa">
    <link rel="stylesheet" href="./css/recss.css">
    <link rel="stylesheet" href="./css/base.css">
    <link rel="stylesheet" href="./vender/fontawesome/css/all.min.css" rel="preload">

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script src="./script/dialog2.js"></script>
    <link rel="stylesheet" href="./css/dialog2.css">

    <style>
        #container {
            display: flex;
            justify-content: center;
            align-items: center;

            width: 100%;
            height: 100vh;
        }

        #login-button {
            display: block;
            margin: 3em auto 0 auto;
        }

        table {
            margin: 0 auto 0 auto;
        }

        tr {
            height: 2.5em;
        }

        td {
            width: 15em;
        }

        #message {
            width: 100%;
            display: block;
            color: #f00;
            font-size: 0.9em;
            text-align: center;
            margin: 2em auto 2em auto;
        }

        label {
            margin-right: 3em;
        }

        .wmessage {
            color: red;
        }
    </style>

</head>

<body>
    <div id="container">
        <div class="login_area">
            <p>
                本処理ではPictPostPersonal Ver.1系統からVer.2系統へのコンバートが行われます。<br>
            </p>
            <p>

            </p>
            <form action='' method='post'>
                <table>
                    <tr>
                        <td><label for='username'>ユーザ名</label></td>
                        <td><input type='text' id='username' name='username' class="flat_input" value=""></td>
                    </tr>
                    <tr>
                        <td><label for='password'>パスワード</label></td>
                        <td><input type='password' id='password' name='password' class="flat_input" value=""></td>
                    </tr>
                </table>
                <input type='submit' name='submit' value='コンバート実行' class="button" id="login-button">

            </form>
        </div>
    </div>
</body>

</html>