package com.aletheia.app.ui.settings

import android.content.ActivityNotFoundException
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Bundle
import android.os.Build
import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.aletheia.app.AletheiaApplication
import com.aletheia.app.R
import com.aletheia.app.data.QBooksUrlPolicy
import com.aletheia.app.data.SettingsRepository
import com.aletheia.app.databinding.FragmentSettingsBinding
import com.aletheia.app.ui.reader.ReaderPreferences
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.text.SimpleDateFormat
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import xyz.kusoft.argusupdater.ArgusAvailableUpdate
import xyz.kusoft.argusupdater.ArgusInstalledApp
import xyz.kusoft.argusupdater.ArgusInstallerEvent
import xyz.kusoft.argusupdater.ArgusInstallerEventState
import xyz.kusoft.argusupdater.ArgusManifest
import xyz.kusoft.argusupdater.ArgusRelease
import xyz.kusoft.argusupdater.ArgusUpdateCheckResult
import xyz.kusoft.argusupdater.ArgusUpdateConfig

class SettingsFragment : Fragment() {

    private var _binding: FragmentSettingsBinding? = null
    private val binding get() = _binding!!

    private val app by lazy { requireActivity().application as AletheiaApplication }
    private val updateConfig = ArgusUpdateConfig(
        baseUrl = ARGUS_BASE_URL,
        slug = ARGUS_SLUG,
        platform = ARGUS_PLATFORM,
        channel = ARGUS_CHANNEL
    )

    private val availableFontSizes = listOf(12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 36, 40)
    private val availableFonts = ReaderPreferences.FONT_NAMES
    private lateinit var availableThemes: List<ThemeOption>
    private var availableUpdate: ArgusAvailableUpdate? = null
    private var latestManifest: ArgusManifest? = null
    private var lastCheckResult: ArgusUpdateCheckResult? = null
    private var isCheckingUpdates = false
    private var isInstallingUpdate = false

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentSettingsBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        availableThemes = listOf(
            ThemeOption("sepia", getString(R.string.reader_warm_theme)),
            ThemeOption("light", getString(R.string.reader_light_theme)),
            ThemeOption("dark", getString(R.string.reader_dark_theme))
        )

        binding.fontSizeDropdown.setSimpleItems(availableFontSizes.map(Int::toString).toTypedArray())
        binding.fontFamilyDropdown.setSimpleItems(availableFonts.toTypedArray())
        binding.themeDropdown.setSimpleItems(availableThemes.map(ThemeOption::label).toTypedArray())

        binding.qbooksUrlInput.doAfterTextChanged {
            updateSecurityHint()
            renderQBooksOverview()
        }

        binding.brightnessSlider.addOnChangeListener { _, value, _ ->
            binding.brightnessValueText.text = getString(R.string.reader_brightness_value, value.toDouble())
        }

        binding.testConnectionButton.setOnClickListener { testConnection() }
        binding.saveSettingsButton.setOnClickListener { saveSettings(showNotification = true) }
        binding.checkUpdatesButton.setOnClickListener { checkForUpdates() }
        binding.installUpdateButton.setOnClickListener { handleUpdateAction() }

        renderQBooksOverview()
        renderUpdateSection()
    }

    override fun onResume() {
        super.onResume()
        loadSettings()
        renderUpdateSection()
    }

    override fun onPause() {
        super.onPause()
        if (_binding != null) {
            saveSettings(showNotification = false)
        }
    }

    override fun onDestroyView() {
        _binding = null
        super.onDestroyView()
    }

    private fun loadSettings() {
        val settings = app.settingsRepository
        val storedTheme = settings.getString(SettingsRepository.KEY_THEME, "sepia")

        binding.qbooksUrlInput.setText(settings.getCatalogUrl())
        binding.qbooksUsernameInput.setText(
            settings.getString(
                SettingsRepository.KEY_QBOOKS_USERNAME,
                settings.getString(SettingsRepository.KEY_LEGACY_CATALOG_USERNAME)
            )
        )
        binding.qbooksPasswordInput.setText(settings.getSecurePassword())
        binding.fontSizeDropdown.setText(settings.getInt(SettingsRepository.KEY_DEFAULT_FONT_SIZE, 20).toString(), false)
        binding.fontFamilyDropdown.setText(
            settings.getString(SettingsRepository.KEY_DEFAULT_FONT_FAMILY, ReaderPreferences.DEFAULT_FONT)
                .takeIf(availableFonts::contains) ?: ReaderPreferences.DEFAULT_FONT,
            false
        )
        binding.themeDropdown.setText(themeLabelFromStored(storedTheme), false)

        val brightnessSlider = binding.brightnessSlider
        val storedBrightness = settings.getDouble(SettingsRepository.KEY_BRIGHTNESS, 100.0).toFloat()
        val brightness = if (storedBrightness.isFinite()) {
            storedBrightness.coerceIn(brightnessSlider.valueFrom, brightnessSlider.valueTo)
        } else {
            brightnessSlider.valueTo
        }
        brightnessSlider.value = brightness
        binding.brightnessValueText.text = getString(R.string.reader_brightness_value, brightness.toDouble())

        updateSecurityHint()
        renderQBooksOverview()
        setConnectionStatus("", Color.parseColor("#6E5648"))
        binding.connectionProgress.visibility = View.GONE
    }

    private fun renderUpdateSection() {
        val binding = _binding ?: return
        val installedApp = loadInstalledApp()
        val canRequestPackageInstalls = app.appUpdateManager.canRequestPackageInstalls()
        val lastInstallerEvent = app.appUpdateManager.loadLastInstallerEvent()
        val installRequestedForCurrentRelease = lastInstallerEvent != null &&
            availableUpdate?.manifest?.release?.version == lastInstallerEvent.releaseVersion &&
            (
                lastInstallerEvent.state == ArgusInstallerEventState.Submitted ||
                    lastInstallerEvent.state == ArgusInstallerEventState.ApprovalRequired
                )

        binding.updateCurrentVersionText.text = getString(
            R.string.settings_update_current_version_value,
            installedApp.versionName ?: "?"
        )
        renderUpdateOverview(installedApp, canRequestPackageInstalls)

        val manifest = latestManifest
        if (manifest == null) {
            binding.updateReleaseLabel.visibility = View.GONE
            binding.updateReleaseText.visibility = View.GONE
            binding.updateReleaseMetaText.visibility = View.GONE
            binding.updateReleaseNotesText.visibility = View.GONE
        } else {
            binding.updateReleaseLabel.visibility = View.VISIBLE
            binding.updateReleaseText.visibility = View.VISIBLE
            binding.updateReleaseMetaText.visibility = View.VISIBLE
            binding.updateReleaseText.text = releaseVersionText(manifest.release)
            binding.updateReleaseMetaText.text = buildReleaseMetaText(manifest)

            val releaseNotes = manifest.release.notes?.trim().orEmpty()
            if (releaseNotes.isBlank()) {
                binding.updateReleaseNotesText.visibility = View.GONE
            } else {
                binding.updateReleaseNotesText.visibility = View.VISIBLE
                binding.updateReleaseNotesText.text = getString(
                    R.string.settings_update_release_note_prefix,
                    releaseNotes
                )
            }
        }

        val status = buildUpdateStatus(installedApp, canRequestPackageInstalls)
        binding.updateStatusTitleText.text = status.title
        binding.updateStatusDetailText.text = status.detail

        if (lastInstallerEvent == null) {
            binding.updateInstallerEventText.visibility = View.GONE
        } else {
            binding.updateInstallerEventText.visibility = View.VISIBLE
            binding.updateInstallerEventText.text = buildInstallerEventText(lastInstallerEvent)
        }

        binding.updateProgress.visibility = if (isCheckingUpdates || isInstallingUpdate) View.VISIBLE else View.GONE
        binding.checkUpdatesButton.isEnabled = !isCheckingUpdates && !isInstallingUpdate

        if (availableUpdate == null) {
            binding.installUpdateButton.visibility = View.GONE
        } else {
            binding.installUpdateButton.visibility = View.VISIBLE
            binding.installUpdateButton.text = getString(
                if (canRequestPackageInstalls) R.string.action_install_update else R.string.action_allow_install
            )
            binding.installUpdateButton.isEnabled = !isCheckingUpdates && !isInstallingUpdate && !installRequestedForCurrentRelease
        }
    }

    private fun renderQBooksOverview() {
        val binding = _binding ?: return
        val url = binding.qbooksUrlInput.text?.toString().orEmpty().trim()
        val validation = QBooksUrlPolicy.validate(url)
        val host = validation.host ?: url
        val (text, color) = when {
            url.isBlank() -> getString(R.string.settings_overview_qbooks_missing) to Color.parseColor("#6E5648")
            !validation.isAllowed -> getString(R.string.settings_overview_qbooks_invalid) to Color.parseColor("#B64932")
            validation.scheme == "https" -> getString(R.string.settings_overview_qbooks_https, host) to Color.parseColor("#2F7D5A")
            validation.scheme == "http" -> getString(R.string.settings_overview_qbooks_local_http, host) to Color.parseColor("#9A672B")
            else -> getString(R.string.settings_overview_qbooks_invalid) to Color.parseColor("#B64932")
        }

        binding.settingsQbooksOverviewDetailText.text = text
        binding.settingsQbooksOverviewDetailText.setTextColor(color)
    }

    private fun renderUpdateOverview(installedApp: ArgusInstalledApp, canRequestPackageInstalls: Boolean) {
        val binding = _binding ?: return
        val (text, color) = when {
            isCheckingUpdates -> getString(R.string.settings_overview_update_checking) to Color.parseColor("#3F6F5A")
            isInstallingUpdate -> getString(R.string.settings_overview_update_installing) to Color.parseColor("#3F6F5A")
            lastCheckResult is ArgusUpdateCheckResult.NotPublished -> {
                getString(R.string.settings_overview_update_not_published) to Color.parseColor("#B64932")
            }
            lastCheckResult is ArgusUpdateCheckResult.Incompatible -> {
                getString(R.string.settings_overview_update_incompatible) to Color.parseColor("#B64932")
            }
            lastCheckResult is ArgusUpdateCheckResult.Failed -> {
                getString(R.string.settings_overview_update_failed) to Color.parseColor("#B64932")
            }
            lastCheckResult is ArgusUpdateCheckResult.UpdateAvailable && !canRequestPackageInstalls -> {
                getString(R.string.settings_overview_update_permission) to Color.parseColor("#9A672B")
            }
            lastCheckResult is ArgusUpdateCheckResult.UpdateAvailable -> {
                val release = (lastCheckResult as ArgusUpdateCheckResult.UpdateAvailable).update.manifest.release
                overviewUpdateAvailableText(release) to Color.parseColor("#9A672B")
            }
            lastCheckResult is ArgusUpdateCheckResult.UpToDate -> {
                val release = (lastCheckResult as ArgusUpdateCheckResult.UpToDate).manifest?.release
                val text = if (release == null) {
                    getString(
                        R.string.settings_overview_update_current,
                        installedApp.versionName ?: "?"
                    )
                } else {
                    overviewUpdateCurrentText(release)
                }
                text to Color.parseColor("#2F7D5A")
            }
            else -> {
                getString(
                    R.string.settings_overview_update_idle,
                    installedApp.versionName ?: "?"
                ) to Color.parseColor("#6E5648")
            }
        }

        binding.settingsUpdateOverviewDetailText.text = text
        binding.settingsUpdateOverviewDetailText.setTextColor(color)
    }

    private fun checkForUpdates() {
        if (isCheckingUpdates || isInstallingUpdate) {
            return
        }

        val installedApp = loadInstalledApp()
        isCheckingUpdates = true
        renderUpdateSection()

        viewLifecycleOwner.lifecycleScope.launch {
            val result = withContext(Dispatchers.IO) {
                app.appUpdateManager.checkForUpdate(updateConfig, installedApp)
            }

            if (_binding == null) {
                return@launch
            }

            isCheckingUpdates = false
            lastCheckResult = result

            when (result) {
                is ArgusUpdateCheckResult.UpToDate -> {
                    availableUpdate = null
                    latestManifest = result.manifest
                }

                is ArgusUpdateCheckResult.UpdateAvailable -> {
                    availableUpdate = result.update
                    latestManifest = result.update.manifest
                }

                is ArgusUpdateCheckResult.NotPublished,
                is ArgusUpdateCheckResult.Incompatible,
                is ArgusUpdateCheckResult.Failed -> {
                    availableUpdate = null
                    latestManifest = null
                }
            }

            renderUpdateSection()
        }
    }

    private fun handleUpdateAction() {
        val update = availableUpdate ?: return
        if (isCheckingUpdates || isInstallingUpdate) {
            return
        }

        if (!app.appUpdateManager.canRequestPackageInstalls()) {
            openUnknownSourcesSettings()
            return
        }

        isInstallingUpdate = true
        renderUpdateSection()

        viewLifecycleOwner.lifecycleScope.launch {
            withContext(Dispatchers.IO) {
                app.appUpdateManager.downloadAndInstall(update)
            }

            if (_binding == null) {
                return@launch
            }

            isInstallingUpdate = false
            renderUpdateSection()
        }
    }

    private fun openUnknownSourcesSettings() {
        try {
            startActivity(app.appUpdateManager.buildUnknownSourcesIntent())
        } catch (error: ActivityNotFoundException) {
            MaterialAlertDialogBuilder(requireContext())
                .setTitle(R.string.dialog_error_title)
                .setMessage(error.message ?: getString(R.string.settings_update_status_permission_detail))
                .setPositiveButton(R.string.action_ok, null)
                .show()
        }
    }

    private fun buildUpdateStatus(installedApp: ArgusInstalledApp, canRequestPackageInstalls: Boolean): UpdateStatus =
        when {
            isCheckingUpdates -> UpdateStatus(
                title = getString(R.string.settings_update_status_checking_title),
                detail = getString(R.string.settings_update_status_checking_detail)
            )

            isInstallingUpdate -> UpdateStatus(
                title = getString(R.string.settings_update_status_installing_title),
                detail = getString(R.string.settings_update_status_installing_detail)
            )

            lastCheckResult is ArgusUpdateCheckResult.NotPublished -> UpdateStatus(
                title = getString(R.string.settings_update_status_not_published_title),
                detail = (lastCheckResult as ArgusUpdateCheckResult.NotPublished).reason
            )

            lastCheckResult is ArgusUpdateCheckResult.Incompatible -> UpdateStatus(
                title = getString(R.string.settings_update_status_incompatible_title),
                detail = (lastCheckResult as ArgusUpdateCheckResult.Incompatible).reason
            )

            lastCheckResult is ArgusUpdateCheckResult.Failed -> UpdateStatus(
                title = getString(R.string.settings_update_status_failed_title),
                detail = (lastCheckResult as ArgusUpdateCheckResult.Failed).reason
            )

            lastCheckResult is ArgusUpdateCheckResult.UpdateAvailable && !canRequestPackageInstalls -> UpdateStatus(
                title = getString(R.string.settings_update_status_permission_title),
                detail = getString(R.string.settings_update_status_permission_detail)
            )

            lastCheckResult is ArgusUpdateCheckResult.UpdateAvailable -> {
                val release = (lastCheckResult as ArgusUpdateCheckResult.UpdateAvailable).update.manifest.release
                UpdateStatus(
                    title = getString(R.string.settings_update_status_available_title),
                    detail = updateAvailableDetailText(release)
                )
            }

            lastCheckResult is ArgusUpdateCheckResult.UpToDate -> {
                val release = (lastCheckResult as ArgusUpdateCheckResult.UpToDate).manifest?.release
                UpdateStatus(
                    title = getString(R.string.settings_update_status_up_to_date_title),
                    detail = if (release == null) {
                        getString(
                            R.string.settings_update_status_up_to_date_detail,
                            installedApp.versionName ?: "?"
                        )
                    } else {
                        updateCurrentDetailText(release)
                    }
                )
            }

            else -> UpdateStatus(
                title = getString(R.string.settings_update_status_idle_title),
                detail = getString(R.string.settings_update_status_idle_detail)
            )
        }

    private fun buildReleaseMetaText(manifest: ArgusManifest): String {
        val sizeText = Formatter.formatShortFileSize(requireContext(), manifest.release.packageSizeBytes)
        val publishedAtText = formatPublishedAt(manifest.release.publishedAt)
        return if (publishedAtText.isBlank()) {
            sizeText
        } else {
            getString(R.string.settings_update_release_meta_template, sizeText, publishedAtText)
        }
    }

    private fun buildInstallerEventText(event: ArgusInstallerEvent): String {
        val stateLabel = when (event.state) {
            ArgusInstallerEventState.Submitted -> getString(R.string.settings_update_installer_state_submitted)
            ArgusInstallerEventState.ApprovalRequired -> getString(R.string.settings_update_installer_state_approval_required)
            ArgusInstallerEventState.Installed -> getString(R.string.settings_update_installer_state_installed)
            ArgusInstallerEventState.Failed -> getString(R.string.settings_update_installer_state_failed)
            else -> event.state
        }
        val detail = event.detail?.trim().orEmpty()
        return if (detail.isBlank()) {
            stateLabel
        } else {
            getString(R.string.settings_update_installer_event_prefix, stateLabel, detail)
        }
    }

    private fun releaseVersionText(release: ArgusRelease): String =
        getString(R.string.settings_update_release_value, release.resolvedVersionName)

    private fun overviewUpdateAvailableText(release: ArgusRelease): String =
        getString(R.string.settings_overview_update_available, release.resolvedVersionName)

    private fun overviewUpdateCurrentText(release: ArgusRelease): String =
        getString(R.string.settings_overview_update_current, release.resolvedVersionName)

    private fun updateAvailableDetailText(release: ArgusRelease): String =
        getString(
            R.string.settings_update_status_available_detail,
            release.resolvedVersionName,
            getString(R.string.action_install_update)
        )

    private fun updateCurrentDetailText(release: ArgusRelease): String =
        getString(R.string.settings_update_status_up_to_date_detail, release.resolvedVersionName)

    private fun loadInstalledApp(): ArgusInstalledApp {
        val context = requireContext().applicationContext
        val packageInfo = loadPackageInfo(context.packageManager, context.packageName)
        return ArgusInstalledApp(
            packageName = context.packageName,
            versionCode = packageInfo.versionCodeCompat(),
            versionName = packageInfo.versionName
        )
    }

    private fun loadPackageInfo(packageManager: PackageManager, packageName: String): PackageInfo =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
        } else {
            @Suppress("DEPRECATION")
            packageManager.getPackageInfo(packageName, 0)
        }

    private fun formatPublishedAt(value: String): String {
        val normalized = value.trim()
        if (normalized.isBlank()) {
            return ""
        }

        val withoutFraction = normalized.replace(Regex("\\.\\d+"), "")
        var parsedDate = null as java.util.Date?
        for (pattern in listOf(
            "yyyy-MM-dd'T'HH:mm:ssXXX",
            "yyyy-MM-dd'T'HH:mm:ssX"
        )) {
            parsedDate = runCatching {
                SimpleDateFormat(pattern, Locale.US).parse(withoutFraction)
            }.getOrNull()
            if (parsedDate != null) {
                break
            }
        }

        return if (parsedDate == null) {
            withoutFraction.replace('T', ' ')
        } else {
            SimpleDateFormat("dd.MM.yyyy HH:mm", Locale.forLanguageTag("ru")).format(parsedDate)
        }
    }

    private fun saveSettings(showNotification: Boolean) {
        val url = binding.qbooksUrlInput.text?.toString().orEmpty().trim()
        val username = binding.qbooksUsernameInput.text?.toString().orEmpty().trim()
        val password = binding.qbooksPasswordInput.text?.toString().orEmpty()
        val fontSize = binding.fontSizeDropdown.text?.toString()?.toIntOrNull() ?: 20
        val fontFamily = binding.fontFamilyDropdown.text?.toString()
            ?.takeIf(availableFonts::contains) ?: ReaderPreferences.DEFAULT_FONT
        val theme = storedThemeFromLabel(binding.themeDropdown.text?.toString().orEmpty())
        val brightness = binding.brightnessSlider.value.toDouble()

        app.settingsRepository.setString(SettingsRepository.KEY_QBOOKS_URL, url)
        app.settingsRepository.setString(SettingsRepository.KEY_QBOOKS_USERNAME, username)
        app.settingsRepository.setSecurePassword(password)
        app.settingsRepository.setInt(SettingsRepository.KEY_DEFAULT_FONT_SIZE, fontSize)
        app.settingsRepository.setString(SettingsRepository.KEY_DEFAULT_FONT_FAMILY, fontFamily)
        app.settingsRepository.setString(SettingsRepository.KEY_THEME, theme)
        app.settingsRepository.setDouble(SettingsRepository.KEY_BRIGHTNESS, brightness)

        if (url.isNotBlank() && QBooksUrlPolicy.isAllowed(url)) {
            runCatching { app.qBooksService.configure(url, username, password) }
        }

        if (showNotification) {
            MaterialAlertDialogBuilder(requireContext())
                .setTitle(R.string.dialog_settings_title)
                .setMessage(R.string.status_settings_saved)
                .setPositiveButton(R.string.action_ok, null)
                .show()
        }
    }

    private fun testConnection() {
        val url = binding.qbooksUrlInput.text?.toString().orEmpty().trim()
        val validationMessage = validateUrl(url)
        if (validationMessage != null) {
            setConnectionStatus(validationMessage, Color.parseColor("#B64932"))
            return
        }

        val username = binding.qbooksUsernameInput.text?.toString().orEmpty().trim()
        val password = binding.qbooksPasswordInput.text?.toString().orEmpty()

        binding.connectionProgress.visibility = View.VISIBLE
        setConnectionStatus(getString(R.string.settings_testing_connection), Color.parseColor("#6E5648"))

        viewLifecycleOwner.lifecycleScope.launch {
            val result = withContext(Dispatchers.IO) {
                runCatching { app.qBooksService.testConnection(url, username, password) }
            }

            binding.connectionProgress.visibility = View.GONE
            result.onSuccess { success ->
                when (success) {
                    is com.aletheia.app.data.Result.Success -> {
                        saveSettings(showNotification = false)
                        setConnectionStatus(getString(R.string.settings_connection_success), Color.parseColor("#2F7D5A"))
                    }

                    is com.aletheia.app.data.Result.Failure -> {
                        setConnectionStatus(success.message, Color.parseColor("#B64932"))
                    }
                }
            }.onFailure { exception ->
                setConnectionStatus(
                    getString(R.string.settings_error_prefix, exception.message ?: "Unknown error"),
                    Color.parseColor("#B64932")
                )
            }
        }
    }

    private fun updateSecurityHint() {
        val url = binding.qbooksUrlInput.text?.toString().orEmpty().trim()
        val validation = QBooksUrlPolicy.validate(url)
        val (text, color) = when {
            url.isBlank() -> getString(R.string.settings_security_empty) to Color.parseColor("#6E5648")
            validation.reason == QBooksUrlPolicy.Reason.InvalidUri -> getString(R.string.settings_security_invalid) to Color.parseColor("#B64932")
            validation.reason == QBooksUrlPolicy.Reason.UnsupportedScheme -> getString(R.string.settings_security_unsupported) to Color.parseColor("#B64932")
            validation.reason == QBooksUrlPolicy.Reason.ExternalCleartext -> getString(R.string.settings_url_cleartext_external) to Color.parseColor("#B64932")
            QBooksUrlPolicy.isHttps(url) -> getString(R.string.settings_security_https) to Color.parseColor("#2F7D5A")
            QBooksUrlPolicy.isAllowedCleartext(url) -> getString(R.string.settings_security_http) to Color.parseColor("#C47A3E")
            else -> getString(R.string.settings_security_invalid) to Color.parseColor("#B64932")
        }

        binding.securityHintText.text = text
        binding.securityHintText.setTextColor(color)
    }

    private fun validateUrl(url: String): String? {
        if (url.isBlank()) {
            return getString(R.string.settings_url_required)
        }
        val validation = QBooksUrlPolicy.validate(url)
        if (!validation.isAllowed) {
            return when (validation.reason) {
                QBooksUrlPolicy.Reason.Blank -> getString(R.string.settings_url_required)
                QBooksUrlPolicy.Reason.InvalidUri -> getString(R.string.settings_url_invalid)
                QBooksUrlPolicy.Reason.UnsupportedScheme -> getString(R.string.settings_url_scheme_invalid)
                QBooksUrlPolicy.Reason.ExternalCleartext -> getString(R.string.settings_url_cleartext_external)
                QBooksUrlPolicy.Reason.Allowed -> null
            }
        }
        return null
    }

    private fun setConnectionStatus(text: String, color: Int) {
        binding.connectionStatusText.text = text
        binding.connectionStatusText.setTextColor(color)
    }

    private fun themeLabelFromStored(theme: String): String =
        availableThemes.firstOrNull { it.storedValue == theme }?.label ?: getString(R.string.reader_light_theme)

    private fun storedThemeFromLabel(label: String): String =
        availableThemes.firstOrNull { it.label == label }?.storedValue ?: ReaderPreferences.THEME_LIGHT

    companion object {
        private const val ARGUS_BASE_URL = "https://argus.kusoft.xyz"
        private const val ARGUS_SLUG = "aletheia-kotlin"
        private const val ARGUS_PLATFORM = "android"
        private const val ARGUS_CHANNEL = "stable"

        fun newInstance() = SettingsFragment()
    }

    private data class ThemeOption(
        val storedValue: String,
        val label: String
    )

    private data class UpdateStatus(
        val title: String,
        val detail: String
    )
}

private fun PackageInfo.versionCodeCompat(): Long =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        longVersionCode
    } else {
        @Suppress("DEPRECATION")
        versionCode.toLong()
    }
