/*
 * Copyright (C) 2024-present Puter Technologies Inc.
 *
 * This file is part of Puter.
 *
 * Puter is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
'use strict';
const deep_proto_merge = require('./config/deep_proto_merge');
// const reserved_words = require('./config/reserved_words');

let config = {};
config.__import_identity__ = require('uuid').v4();

// Static defaults
config.servers = [];

config.disable_user_signup = false;
config.default_user_group = '78b1b1dd-c959-44d2-b02c-8735671f9997';

// Will disable the auto-generated temp users. If a user lands on the site, they will be required to sign up or log in.
config.disable_temp_users = false;
config.default_temp_group = 'b7220104-7905-4985-b996-649fdcdb3c8f';

config.max_file_size = 100_000_000_000;
config.max_thumb_size = 1_000;
config.max_fsentry_name_length = 767;

config.username_regex = /^\w+$/;
config.username_max_length = 45;
config.subdomain_regex = /^[a-zA-Z0-9_-]+$/;
config.subdomain_max_length = 60;
config.app_name_regex = /^[a-zA-Z0-9_-]+$/;
config.app_name_max_length = 60;
config.app_title_max_length = 60;
config.min_pass_length = 6;

config.strict_email_verification_required = false;
config.require_email_verification_to_publish_website = false;

config.kv_max_key_size = 1024;
config.kv_max_value_size = 400 * 1024;

// Captcha configuration
config.captcha = {
    enabled: false, // Enable captcha by default
    expirationTime: 10 * 60 * 1000, // 10 minutes default expiration time
    difficulty: 'medium', // Default difficulty level
};

config.monitor = {
    metricsInterval: 60000,
    windowSize: 30,
};

config.max_subdomains_per_user = 2000;
config.storage_capacity = 1 * 1024 * 1024 * 1024;
config.static_hosting_domain = 'site.puter.localhost';

// Storage limiting is set to false by default
// Storage available on the mountpoint/drive puter is running is the storage available
config.is_storage_limited = false;
config.available_device_storage = null;

config.thumb_width = 80;
config.thumb_height = 80;
config.app_max_icon_size = 5 * 1024 * 1024;

config.defaultjs_asset_path = '../../';

config.short_description = 'Puter is a privacy-first personal cloud that houses all your files, apps, and games in one private and secure place, accessible from anywhere at any time.';
config.title = 'Puter';
config.company = 'Puter Technologies Inc.';

config.puter_hosted_data = {
    puter_versions: 'https://version.puter.site/puter_versions.json',
};

{
    const path_ = require('path');
    config.assets = {
        gui: path_.join(__dirname, '../../gui'),
        gui_profile: 'development',
    };
}

// words that cannot be used by others as subdomains or app names
// config.reserved_words = reserved_words;
config.reserved_words = [];

{
    config.reserved_words.push(...require('./config/reserved_words'));
}

// set default S3 settings for this server, if any
if ( config.server_id ) {
    // see if this server has a specific bucket
    for ( const server of config.servers ) {
        if ( server.id !== config.server_id ) continue;
        if ( ! server.s3_bucket ) continue;

        config.s3_bucket = server.s3_bucket;
        config.s3_region = server.region;
    }
}

config.contact_email = `hey@${ config.domain}`;

// TODO: default value will be changed to false in a future release;
//       details to follow in a future announcement.
config.legacy_token_migrate = true;

// === OS Information ===
const os = require('os');
const fs = require('fs');
const { Context, context_config } = require('./util/context');
config.os = {};
config.os.platform = os.platform();

if ( config.os.platform === 'linux' ) {
    try {
        const osRelease = fs.readFileSync('/etc/os-release').toString();
        // CONTRIBUTORS: If this is the behavior you expect, please add your
        //               Linux distro here.
        if ( osRelease.includes('ID=arch') ) {
            config.os.distro = 'arch';
            config.os.archbtw = true;
        }
    } catch (_) {
        // We don't care if we can't read this file;
        // we'll just assume it's not a Linux distro.
    }
}

// config.os.refined specifies if Puter is running within a host environment
// where a higher level of user configuration and control is expected.
config.os.refined = config.os.archbtw;

if ( config.os.refined ) {
    config.no_browser_launch = true;
}

// NEW_CONFIG_LOADING
const maybe_port = config =>
    config.pub_port !== 80 && config.pub_port !== 443 ? `:${ config.pub_port}` : '';

const computed_defaults = {
    pub_port: config => config.http_port,
    origin: config => `${config.protocol }://${ config.domain }${maybe_port(config)}`,
    api_base_url: config => config.experimental_no_subdomain
        ? config.origin
        : `${config.protocol }://api.${ config.domain }${maybe_port(config)}`,
    social_card: config => `${config.origin}/assets/img/screenshot.png`,
};

// We're going to export a config object that's decorated
// with additional behavior
let config_to_export;

// We have a pointer to some config object which
// load_config() may replace
const config_pointer = {};
{
    Object.setPrototypeOf(config_pointer, config);
    config_to_export = config_pointer;
}

// We have some methods that can be called on `config`
{
    // Add configuration values with precedence over the current config
    const load_config = o => {
        let replacement_config = {
            ...o,
        };
        replacement_config = deep_proto_merge(replacement_config, Object.getPrototypeOf(config_pointer), {
            preserve_flag: true,
        });
        Object.setPrototypeOf(config_pointer, replacement_config);
    };

    const config_api = { load_config };
    Object.setPrototypeOf(config_api, config_to_export);
    config_to_export = config_api;
}

// We have some values with computed defaults
{
    const get_implied = (target, prop) => {
        if ( prop in computed_defaults ) {
            return computed_defaults[prop](target);
        }
        return undefined;
    };
    config_to_export = new Proxy(config_to_export, {
        get: (target, prop, receiver) => {
            if ( prop in target ) {
                return target[prop];
            } else {
                return get_implied(config_to_export, prop);
            }
        },
    });
}

// We'd like to store values changed at runtime separately
// for easier runtime debugging
{
    const config_runtime_values = {
        $: 'runtime-values',
    };
    let initialPrototype = config_to_export;
    Object.setPrototypeOf(config_runtime_values, config_to_export);
    config_to_export = config_runtime_values;

    config_to_export.__set_config_object__ = (object, options = {}) => {
        // options for this method
        const replacePrototype = options.replacePrototype ?? true;
        const useInitialPrototype = options.useInitialPrototype ?? true;

        // maybe replace prototype
        if ( replacePrototype ) {
            const newProto = useInitialPrototype
                ? initialPrototype
                : Object.getPrototypeOf(config_runtime_values);
            Object.setPrototypeOf(object, newProto);
        }

        // use this object as the prototype
        Object.setPrototypeOf(config_runtime_values, object);
    };

    // These can be difficult to find and cause painful
    // confusing issues, so we log any time this happens
    config_to_export = new Proxy(config_to_export, {
        set: (target, prop, value, receiver) => {
            const logger = Context.get('logger', { allow_fallback: true });
            // If no logger, just give up
            if ( logger ) {
                logger.debug('\x1B[36;1mCONFIGURATION MUTATED AT RUNTIME\x1B[0m',
                                { prop, value });
            }
            target[prop] = value;
            return true;
        },
    });
}

// We configure the behavior in context.js from here to avoid a cyclic
// mutual dependency between it and this file.
//
// Previously we had this:
// context --(are we in "dev" environment?)--> config
//
// So we could not add this:
// config --(where is the logger?) --> context
//
// So instead we now have:
// config --(read this property to determine 'strict' mode)--> context
// config --(where is the logger?) --> context
//
Object.defineProperty(context_config, 'strict', {
    get: () => config_to_export.env === 'dev',
    configurable: true,
});

module.exports = config_to_export;
