const fs = require('fs-extra')
const got = require('got')
const path = require('path')

const ConfigManager = require('./configmanager')
const { DEFAULT_REMOTE_DISTRO_URL, setRemoteDistributionUrl } = require('./distromanager')

const LOCAL_CATALOG_PATH = path.join(__dirname, '..', 'launcher', 'catalog.json')
const ROOT_ASSET_PATH = path.join(__dirname, '..', '..', '..')
const DEFAULT_REMOTE_CATALOG_URL = normalizeNullableText(process.env.LAUNCHER_CATALOG_URL) || 'http://127.0.0.1:8787/catalog.json'

function normalizeText(value){
    return typeof value === 'string' ? value.trim() : ''
}

function normalizeNullableText(value){
    const nextValue = normalizeText(value)
    return nextValue.length > 0 ? nextValue : null
}

function normalizeBoolean(value){
    return value === true
}

function normalizePositiveInteger(value, fallback, minimum = 1){
    const parsed = Number.parseInt(String(value ?? ''), 10)
    if(Number.isFinite(parsed) && parsed >= minimum){
        return parsed
    }
    return fallback
}

function isRemoteSource(source){
    return /^https?:\/\//i.test(source)
}

function isAbsoluteFileSystemPath(source){
    return path.isAbsolute(source) || /^[a-zA-Z]:[\\/]/.test(source)
}

function deriveFeatureFlags(rawProfile){
    const legacyKind = normalizeText(rawProfile?.kind)

    let modsEnabled = normalizeBoolean(rawProfile?.modsEnabled)
    let pluginsEnabled = normalizeBoolean(rawProfile?.pluginsEnabled)
    let serverEnabled = normalizeBoolean(rawProfile?.serverEnabled)

    if(legacyKind === 'modpack'){
        modsEnabled = true
    } else if(legacyKind === 'server-pack'){
        pluginsEnabled = true
        serverEnabled = true
    }

    if(pluginsEnabled){
        serverEnabled = true
    }

    return {
        modsEnabled,
        pluginsEnabled,
        serverEnabled
    }
}

function deriveLegacyKind(flags){
    if(flags.serverEnabled){
        return 'server-pack'
    }
    if(flags.modsEnabled){
        return 'modpack'
    }
    return 'map'
}

function resolveLegacyServerJar(rawProfile){
    const directValue = normalizeNullableText(rawProfile?.serverJarUrl)
    if(directValue != null){
        return directValue
    }

    const legacyBundle = normalizeNullableText(rawProfile?.serverBundleUrl)
    if(legacyBundle != null && legacyBundle.toLowerCase().endsWith('.jar')){
        return legacyBundle
    }

    return null
}

function resolveProfileSourceValue(value, catalogSource){
    const nextValue = normalizeNullableText(value)
    if(nextValue == null){
        return null
    }

    if(isRemoteSource(nextValue) || nextValue.startsWith('file://') || isAbsoluteFileSystemPath(nextValue)){
        return nextValue
    }

    if(isRemoteSource(catalogSource)){
        return new URL(nextValue, catalogSource).toString()
    }

    return path.resolve(ROOT_ASSET_PATH, nextValue)
}

function toStoredProfile(rawProfile, catalogSource){
    const flags = deriveFeatureFlags(rawProfile)
    const storedProfile = {
        id: normalizeText(rawProfile.id),
        name: normalizeText(rawProfile.name),
        kind: deriveLegacyKind(flags),
        description: normalizeText(rawProfile.description),
        details: normalizeText(rawProfile.details),
        distributionUrl: resolveProfileSourceValue(rawProfile.distributionUrl, catalogSource),
        modsEnabled: flags.modsEnabled,
        pluginsEnabled: flags.pluginsEnabled,
        serverEnabled: flags.serverEnabled,
        worldArchiveUrl: resolveProfileSourceValue(rawProfile.worldArchiveUrl, catalogSource),
        worldDirectoryName: normalizeNullableText(rawProfile.worldDirectoryName),
        serverJarUrl: resolveProfileSourceValue(resolveLegacyServerJar(rawProfile), catalogSource),
        serverDirectoryName: normalizeText(rawProfile.serverDirectoryName) || 'server',
        serverPort: Number.isFinite(Number(rawProfile.serverPort)) ? Number(rawProfile.serverPort) : 25565,
        serverMemoryMb: normalizePositiveInteger(rawProfile.serverMemoryMb, 4096, 512),
        serverMaxPlayers: normalizePositiveInteger(rawProfile.serverMaxPlayers, 20, 1),
        serverWhitelistEnabled: normalizeBoolean(rawProfile.serverWhitelistEnabled),
        artwork: resolveProfileSourceValue(rawProfile.artwork, catalogSource) ?? ''
    }

    if(!storedProfile.serverEnabled){
        storedProfile.serverJarUrl = null
        storedProfile.serverDirectoryName = 'server'
        storedProfile.serverPort = 25565
        storedProfile.serverMemoryMb = 4096
        storedProfile.serverMaxPlayers = 20
        storedProfile.serverWhitelistEnabled = false
    }

    return storedProfile
}

function normalizeProfile(rawProfile, sourceType = 'catalog', catalogSource = LOCAL_CATALOG_PATH){
    const storedProfile = toStoredProfile(rawProfile, catalogSource)
    const launchIssues = []
    const hostIssues = []

    if(storedProfile.distributionUrl == null){
        launchIssues.push('distribution URL이 필요합니다.')
    }

    if(storedProfile.worldArchiveUrl == null){
        launchIssues.push('맵 ZIP 또는 로컬 월드 경로가 필요합니다.')
    }
    if(storedProfile.worldDirectoryName == null){
        launchIssues.push('월드 폴더 이름이 필요합니다.')
    }

    if(storedProfile.serverEnabled && storedProfile.serverJarUrl == null){
        hostIssues.push('로컬 서버를 시작하려면 버킷 JAR 업로드가 필요합니다.')
    }

    const launchReady = launchIssues.length === 0
    const hostReady = storedProfile.serverEnabled ? hostIssues.length === 0 : false

    return {
        ...storedProfile,
        sourceType,
        isCustom: sourceType === 'custom',
        configured: launchReady,
        launchReady,
        launchIssues,
        hostReady,
        hostIssues
    }
}

function resolveCatalogFileSource(source){
    if(source.startsWith('file://')){
        return decodeURIComponent(source.substring('file://'.length))
    }
    return path.resolve(source)
}

async function readCatalogSource(source){
    if(/^https?:\/\//i.test(source)){
        return got(source, {
            responseType: 'json'
        }).json()
    }

    return fs.readJson(resolveCatalogFileSource(source))
}

exports.getLocalCatalogPath = function(){
    return LOCAL_CATALOG_PATH
}

exports.getDefaultRemoteCatalogUrl = function(){
    return DEFAULT_REMOTE_CATALOG_URL
}

function buildCatalogSourceCandidates(configuredSource){
    const trimmedSource = normalizeNullableText(configuredSource)
    if(trimmedSource != null){
        return [trimmedSource]
    }

    return [DEFAULT_REMOTE_CATALOG_URL, LOCAL_CATALOG_PATH]
}

exports.loadCatalog = async function(){
    const configuredSource = ConfigManager.getLibraryCatalogSource()
    const sourceCandidates = buildCatalogSourceCandidates(configuredSource)
    let rawCatalog = {
        version: 1,
        profiles: []
    }
    let sourceError = null
    let source = sourceCandidates[sourceCandidates.length - 1]

    for(const candidate of sourceCandidates){
        try {
            rawCatalog = await readCatalogSource(candidate)
            source = candidate
            sourceError = null
            break
        } catch (error) {
            source = candidate
            sourceError = error
        }
    }

    const rawProfiles = Array.isArray(rawCatalog.profiles) ? rawCatalog.profiles : []

    return {
        version: rawCatalog.version ?? 1,
        source,
        sourceError,
        profiles: rawProfiles
            .filter((profile) => profile != null && typeof profile.id === 'string' && typeof profile.name === 'string')
            .map((profile) => normalizeProfile(profile, 'catalog', source))
            .sort((left, right) => left.name.localeCompare(right.name, 'ko'))
    }
}

exports.getInstalledProfiles = async function(){
    const catalog = await exports.loadCatalog()
    const installedProfiles = ConfigManager.getInstalledLibraryProfiles()

    const mergedProfiles = installedProfiles.map((installedProfile) => {
        const latestProfile = catalog.profiles.find((profile) => profile.id === installedProfile.id)
        return latestProfile != null
            ? {
                ...installedProfile,
                ...latestProfile,
                installedAt: installedProfile.installedAt
            }
            : installedProfile
    })

    ConfigManager.setInstalledLibraryProfiles(mergedProfiles)
    ConfigManager.save()

    return mergedProfiles
}

exports.installProfile = async function(profileId){
    const catalog = await exports.loadCatalog()
    const profile = catalog.profiles.find((entry) => entry.id === profileId)

    if(profile == null){
        throw new Error(`Unknown profile: ${profileId}`)
    }

    const installedProfile = {
        ...profile,
        installedAt: new Date().toISOString()
    }

    ConfigManager.upsertInstalledLibraryProfile(installedProfile)

    if(ConfigManager.getSelectedLibraryProfile() == null){
        ConfigManager.setSelectedLibraryProfile(profile.id)
    }

    ConfigManager.save()
    return installedProfile
}

exports.removeProfile = function(profileId){
    ConfigManager.removeInstalledLibraryProfile(profileId)
    ConfigManager.save()
}

exports.selectProfile = function(profileId){
    ConfigManager.setSelectedLibraryProfile(profileId)
    ConfigManager.save()
}

exports.getSelectedProfileId = function(){
    return ConfigManager.getSelectedLibraryProfile()
}

exports.getSelectedProfileSync = function(){
    const selectedProfileId = ConfigManager.getSelectedLibraryProfile()
    if(selectedProfileId == null){
        return null
    }
    return ConfigManager.getInstalledLibraryProfile(selectedProfileId)
}

exports.resolveServerAddress = function(profile){
    if(profile == null){
        return null
    }

    const manualAddress = ConfigManager.getLibraryServerAddressOverride(profile.id)
    if(manualAddress != null && manualAddress.length > 0){
        return manualAddress
    }

    return null
}

exports.setServerAddressOverride = function(profileId, address){
    ConfigManager.setLibraryServerAddressOverride(profileId, address)
    ConfigManager.save()
}

exports.shouldHostLocally = function(profile){
    if(profile == null || profile.serverEnabled !== true){
        return false
    }

    return exports.resolveServerAddress(profile) == null
}

exports.applyConfiguredProfile = function(){
    const selectedProfile = exports.getSelectedProfileSync()
    const distributionUrl = selectedProfile?.distributionUrl ?? DEFAULT_REMOTE_DISTRO_URL
    setRemoteDistributionUrl(distributionUrl)
    return selectedProfile
}
