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')

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

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

function toStoredProfile(rawProfile){
    const kind = normalizeText(rawProfile.kind) || 'modpack'
    const storedProfile = {
        id: normalizeText(rawProfile.id),
        name: normalizeText(rawProfile.name),
        kind,
        description: normalizeText(rawProfile.description),
        details: normalizeText(rawProfile.details),
        distributionUrl: normalizeNullableText(rawProfile.distributionUrl),
        defaultServerAddress: normalizeText(rawProfile.defaultServerAddress),
        allowCustomServerAddress: rawProfile.allowCustomServerAddress === true,
        worldArchiveUrl: normalizeNullableText(rawProfile.worldArchiveUrl),
        worldDirectoryName: normalizeNullableText(rawProfile.worldDirectoryName),
        serverBundleUrl: normalizeNullableText(rawProfile.serverBundleUrl),
        serverDirectoryName: normalizeText(rawProfile.serverDirectoryName) || 'server',
        serverLaunchCommand: normalizeNullableText(rawProfile.serverLaunchCommand),
        serverWorkingDirectory: normalizeNullableText(rawProfile.serverWorkingDirectory),
        serverPort: Number.isFinite(Number(rawProfile.serverPort)) ? Number(rawProfile.serverPort) : 25565,
        tunnelCommand: normalizeNullableText(rawProfile.tunnelCommand),
        tunnelAddressRegex: normalizeNullableText(rawProfile.tunnelAddressRegex),
        artwork: normalizeText(rawProfile.artwork)
    }

    if(kind !== 'map'){
        storedProfile.worldArchiveUrl = null
        storedProfile.worldDirectoryName = null
    }

    if(kind !== 'server-pack'){
        storedProfile.serverBundleUrl = null
        storedProfile.serverDirectoryName = 'server'
        storedProfile.serverLaunchCommand = null
        storedProfile.serverWorkingDirectory = null
        storedProfile.serverPort = 25565
        storedProfile.tunnelCommand = null
        storedProfile.tunnelAddressRegex = null
    }

    return storedProfile
}

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

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

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

    if(storedProfile.kind === 'server-pack' && storedProfile.serverBundleUrl == null){
        hostIssues.push('로컬 호스팅을 하려면 서버 번들 ZIP 또는 디렉터리 경로가 필요합니다.')
    }

    const launchReady = launchIssues.length === 0
    const hostReady = storedProfile.kind === 'server-pack' ? 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.loadCatalog = async function(){
    const configuredSource = ConfigManager.getLibraryCatalogSource()
    const source = configuredSource != null && configuredSource.trim().length > 0 ? configuredSource.trim() : LOCAL_CATALOG_PATH
    let rawCatalog = {
        version: 1,
        profiles: []
    }
    let sourceError = null

    try {
        rawCatalog = await readCatalogSource(source)
    } catch (error) {
        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'))
            .sort((left, right) => left.name.localeCompare(right.name, 'ko'))
    }
}

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

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

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
    }

    if(typeof profile.defaultServerAddress === 'string' && profile.defaultServerAddress.length > 0){
        return profile.defaultServerAddress
    }

    return null
}

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

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