
package app.crossword.yourealwaysbe.forkyz

import javax.inject.Inject
import kotlinx.coroutines.launch

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.activity.result.contract.ActivityResultContracts.GetContent
import androidx.activity.result.contract.ActivityResultContracts.OpenDocumentTree
import androidx.activity.viewModels
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle

import dagger.hilt.android.AndroidEntryPoint

import app.crossword.yourealwaysbe.forkyz.theme.ForkyzTopAppBar
import app.crossword.yourealwaysbe.forkyz.util.ExitActivity
import app.crossword.yourealwaysbe.forkyz.util.PuzzleExportService
import app.crossword.yourealwaysbe.forkyz.view.ListDialog
import app.crossword.yourealwaysbe.forkyz.view.MultiListDialog
import app.crossword.yourealwaysbe.forkyz.view.OKCancelDialog
import app.crossword.yourealwaysbe.forkyz.view.SearchBar
import app.crossword.yourealwaysbe.forkyz.view.StringDialog

@AndroidEntryPoint
class SettingsActivity : ForkyzActivity() {
    private val TAG = "ForkyzSettingsActivity"

    private val SETTINGS_HORIZONTAL_PADDING = 20.dp
    private val SETTINGS_VERTICAL_PADDING = 14.dp
    private val SWITCH_PADDING = 4.dp

    private val viewModel by viewModels<SettingsActivityViewModel>()

    private val getSAFURI = registerForActivityResult(OpenDocumentTree()) {
        uri : Uri? -> uri?.let {
            if (!viewModel.setNewExternalStorageSAFURI(it)) {
                Toast.makeText(
                    getApplication(),
                    R.string.failed_to_initialise_saf,
                    Toast.LENGTH_LONG
                ).show()
            }
        }
    }
    private val getImportSettingsURI = registerForActivityResult(GetContent()) {
        uri : Uri? -> uri?.let {
            viewModel.importSettings(uri)
        }
    }
    private val getExportSettingsURI = registerForActivityResult(
        CreateDocument(SETTINGS_MIME_TYPE)
    ) {
        uri : Uri? -> uri?.let {
            viewModel.exportSettings(uri)
        }
    }
    private val getExportPuzzlesURI
        = registerForActivityResult(OpenDocumentTree()) {
            uri : Uri? -> uri?.let {
                try {
                    Log.i(TAG, "Starting puzzle export")
                    val intent = Intent(this, PuzzleExportService::class.java)
                    intent.putExtra(
                        PuzzleExportService.PUZZLE_EXPORT_URI,
                        uri.toString()
                    )
                    ContextCompat.startForegroundService(this, intent)
                // use IllegalState as
                // ForegroundServiceNotAllowedException requires API 31
                } catch (e : IllegalStateException) {
                    Log.i(
                        TAG,
                        "Could not start puzzle export service on foreground"
                    )
                    Toast.makeText(
                       this,
                       R.string.puzzles_export_failed,
                       Toast.LENGTH_SHORT
                    ).show()
                }
            }
        }

    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState : Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            val state by viewModel.state.collectAsStateWithLifecycle()

            LaunchForActivityResults()
            ShowMessages()

            BackHandler(enabled = state.hasBackStack) {
                handleBack()
            }

            AppTheme() {
                // Reset list state/scroll on new list
                // https://slack-chats.kotlinlang.org/t/510683/what-would-be-to-best-way-to-reset-the-scroll-of-a-lazylist-
                val scrollBehavior = key(state.settingsItems) {
                    TopAppBarDefaults.pinnedScrollBehavior()
                }
                val listState = key(state.settingsItems) {
                    rememberLazyListState()
                }

                Scaffold(
                    topBar = { TopAppBar(scrollBehavior) },
                    modifier = Modifier.nestedScroll(
                        scrollBehavior.nestedScrollConnection
                    )
                ) { innerPadding ->
                    Column(modifier = Modifier.padding(innerPadding)) {
                        SearchBar(
                            showSearchBar = state.inSearchMode,
                            searchTerm = state.searchTerm,
                            onValueChange = viewModel::setSearchTerm,
                            onClose = viewModel::endSearch,
                            hint
                                = stringResource(R.string.filter_settings_hint),
                        )

                        LazyColumn(
                            modifier = Modifier.fillMaxWidth(),
                            state = listState,
                        ) {
                            itemsIndexed(state.settingsItems) { index, item ->
                                SettingsItem(
                                    modifier = Modifier
                                        .fillMaxWidth()
                                        .padding(
                                            vertical
                                                = SETTINGS_VERTICAL_PADDING,
                                            horizontal
                                                = SETTINGS_HORIZONTAL_PADDING,
                                        ),
                                    item = item,
                                    isFirst = (index == 0),
                                )
                            }
                        }
                    }

                    DisplayDialogs(state)
                }
            }
        }
    }

    /**
     * Launch for activity results if needed
     *
     * E.g. if a SAF URI is needed, launch getSAFURI
     */
    @Composable
    private fun LaunchForActivityResults() {
        val needsURI by viewModel.needsURI.collectAsStateWithLifecycle()
        when (needsURI) {
            RequestedURI.SAF -> {
                LaunchedEffect(needsURI) {
                    Toast.makeText(
                        getApplication(),
                        R.string.storage_select_saf_info,
                        Toast.LENGTH_LONG
                    ).show()
                    getSAFURI.launch(null)
                    viewModel.clearNeedsURI()
                }
            }
            RequestedURI.SETTINGS_EXPORT -> {
                LaunchedEffect(needsURI) {
                    getExportSettingsURI.launch(SETTINGS_DEFAULT_FILENAME)
                    viewModel.clearNeedsURI()
                }
            }
            RequestedURI.SETTINGS_IMPORT -> {
                LaunchedEffect(needsURI) {
                    getImportSettingsURI.launch(SETTINGS_MIME_TYPE)
                    viewModel.clearNeedsURI()
                }
            }
            RequestedURI.PUZZLES_EXPORT -> {
                LaunchedEffect(needsURI) {
                    getExportPuzzlesURI.launch(null)
                    viewModel.clearNeedsURI()
                }
            }
            RequestedURI.NONE -> {
                // nothing to do
            }
        }
    }

    @Composable
    private fun ShowMessages() {
        val messages by viewModel.messages.collectAsStateWithLifecycle()
        LaunchedEffect(messages) {
            val app = getApplication()
            for (msg in messages) {
                Toast.makeText(app, msg, Toast.LENGTH_LONG).show()
            }
            viewModel.clearMessages()
        }
    }

    @Composable
    @OptIn(ExperimentalMaterial3Api::class)
    private fun TopAppBar(scrollBehavior : TopAppBarScrollBehavior) {
        // using view model in here because it's not intended to be
        // reused elsewhere
        val state by viewModel.state.collectAsStateWithLifecycle()
        themeHelper.ForkyzTopAppBar(
            title = { Text(stringResource(id = state.currentPage.title)) },
            onBack = this::handleBack,
            actions = {
                IconButton(onClick = viewModel::startSearch) {
                    Icon(
                        Icons.Filled.Search,
                        stringResource(R.string.filter_settings_hint),
                    )
                }
            },
            scrollBehavior = scrollBehavior,
        )
    }

    @Composable
    private fun SettingsItem(
        modifier : Modifier,
        item : SettingsItem,
        isFirst : Boolean,
    ) {
        // using viewmodel here as this is just a switch
        when (item) {
            is SettingsDivider -> HorizontalDivider(modifier = modifier)
            is SettingsNamedItem -> {
                val title = stringResource(item.title)
                val summary = item.summary?.let { stringResource(item.summary) }
                val errorMsg = item.errorMsg?.collectAsStateWithLifecycle()

                when (item) {
                    is SettingsSubPage -> {
                        SettingsClickable(
                            modifier = modifier,
                            title = title,
                            summary = summary,
                            errorMsg = errorMsg?.value,
                            onClick = { viewModel.setCurrentPage(item.subpage) }
                        )
                    }
                    is SettingsBoolean -> {
                        val state by item.value.observeAsState()
                        SettingsBoolean(
                            modifier = modifier,
                            title = title,
                            errorMsg = errorMsg?.value,
                            summary = summary,
                            value = state ?: false,
                            onValueChange = item.setValue,
                        )
                    }
                    is SettingsTriState -> {
                        val state by item.value.observeAsState()
                        SettingsTriState(
                            modifier = modifier,
                            title = title,
                            summary = summary,
                            value = state ?: TriState.OFF,
                            onValueChange = item.setValue,
                            errorMsg = errorMsg?.value,
                        )
                    }
                    is SettingsInputItem -> {
                        SettingsClickable(
                            modifier = modifier,
                            title = title,
                            summary = summary,
                            errorMsg = errorMsg?.value,
                            onClick = { viewModel.openInputSetting(item) },
                        )
                    }
                    is SettingsHeading -> {
                        SettingsHeading(
                            modifier = modifier,
                            title = title,
                            summary = summary,
                            errorMsg = errorMsg?.value,
                            withRule = !isFirst
                        )
                    }
                    is SettingsHTMLPage -> {
                        SettingsClickable(
                            modifier = modifier,
                            title = title,
                            summary = summary,
                            errorMsg = errorMsg?.value,
                            onClick = { openHTMLPage(item.url) },
                        )
                    }
                    is SettingsAction -> {
                        SettingsClickable(
                            modifier = modifier,
                            title = title,
                            summary = summary,
                            errorMsg = errorMsg?.value,
                            onClick = item.onClick,
                        )
                    }
                    is SettingsLogcat -> {
                        SettingsClickable(
                            modifier = modifier,
                            title = title,
                            summary = summary,
                            errorMsg = errorMsg?.value,
                            onClick = {
                                startActivity(
                                    Intent(this, LogcatActivity::class.java)
                                )
                            },
                        )
                    }
                    else -> { } /* ignore */
                }
            }
            else -> { } /* ignore */
        }
    }

    @Composable
    private fun SettingsClickable(
        modifier : Modifier,
        title : String,
        summary : String?,
        errorMsg : String?,
        onClick : () -> Unit,
    ) {
        SettingsDescription(
            modifier = Modifier.clickable { onClick() }
                .then(modifier),
            title = title,
            summary = summary,
            errorMsg = errorMsg,
        )
    }

    @Composable
    fun SettingsBoolean(
        modifier : Modifier,
        title : String,
        summary : String?,
        errorMsg : String?,
        value : Boolean,
        onValueChange : (Boolean) -> Unit,
    ) {
        Row(
            modifier = Modifier
                .clickable { onValueChange(!value) }
                .then(modifier),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            SettingsDescription(
                modifier = Modifier.weight(1.0f),
                title = title,
                summary = summary,
                errorMsg = errorMsg,
            )
            Switch(
                modifier = Modifier.padding(start = SWITCH_PADDING),
                checked = value,
                onCheckedChange = onValueChange,
            )
        }
    }

    @Composable
    fun SettingsTriState(
        modifier : Modifier,
        title : String,
        summary : String?,
        errorMsg : String?,
        value : TriState,
        onValueChange : (TriState) -> Unit,
    ) {
        fun onBooleanChange(newBoolean : Boolean) {
            onValueChange(if (newBoolean) TriState.ON else TriState.OFF)
        }

        val booleanValue = value != TriState.OFF
        val colors = if (value == TriState.INDETERMINATE) {
            val defaults = SwitchDefaults.colors()
            SwitchDefaults.colors(
                checkedThumbColor = defaults.uncheckedThumbColor,
                checkedTrackColor = defaults.uncheckedTrackColor,
                checkedBorderColor = defaults.uncheckedBorderColor,
            )
        } else {
            SwitchDefaults.colors()
        }

        Row(
            modifier = Modifier
                .clickable { onBooleanChange(!booleanValue) }
                .then(modifier),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            SettingsDescription(
                modifier = Modifier.weight(1.0f),
                title = title,
                summary = summary,
                errorMsg = errorMsg,
            )
            Switch(
                modifier = Modifier.padding(start = SWITCH_PADDING),
                checked = booleanValue,
                onCheckedChange = { onBooleanChange(it) },
                colors = colors,
            )
        }
    }

    @Composable
    private fun SettingsDescription(
        modifier : Modifier,
        title : String,
        summary : String?,
        errorMsg : String?,
    ) {
        Column(modifier = modifier) {
            Text(
                title,
                style = MaterialTheme.typography.titleMedium
            )
            summary?.let {
                Text(summary)
            }
            errorMsg?.let {
                Text(
                    errorMsg,
                    color = MaterialTheme.colorScheme.error,
                )
            }
        }
    }

    @Composable
    private fun SettingsHeading(
        modifier : Modifier,
        title : String,
        summary : String?,
        errorMsg : String?,
        withRule : Boolean,
    ) {
        Column(modifier = modifier) {
            if (withRule) {
                HorizontalDivider(
                    modifier = Modifier.padding(
                        bottom=SETTINGS_VERTICAL_PADDING
                    )
                )
            }
            Text(
                title,
                style = MaterialTheme.typography.titleMedium,
                color = MaterialTheme.colorScheme.primary,
            )
            summary?.let {
                Text(it)
            }
            errorMsg?.let {
                Text(
                    it,
                    color = MaterialTheme.colorScheme.error,
                )
            }
        }
    }

    @Composable
    private fun DisplayDialogs(state : SettingsActivityViewState) {
        if (state.storageChangedAppExit) {
            StorageChangedAppExitDialog()
        }

        state.activeInputItem?.let {
            when (it) {
                is SettingsDynamicList<*> -> ActiveListItemDialog(it)
                is SettingsDynamicMultiList<*> -> ActiveMultiListItemDialog(it)
                is SettingsString -> ActiveStringItemDialog(it)
                is SettingsInteger -> ActiveIntegerItemDialog(it)
                else -> { }
            }
        }
    }

    @Composable
    private fun StorageChangedAppExitDialog() {
        AlertDialog(
            title = { Text(stringResource(R.string.storage_changed_title)) },
            text = {
                Text(stringResource(R.string.storage_changed_please_restart))
            },
            onDismissRequest = this::appExit,
            confirmButton = {
                TextButton(onClick = this::appExit) {
                    Text(stringResource(R.string.exit))
                }
            },
        )
    }

    @Composable
    private fun ActiveListItemDialog(item : SettingsDynamicList<*>) {
        val selected by item.value.observeAsState()
        val entries by item.entries.observeAsState()

        val labels = entries?.map {
                it.labelId?.let {
                    stringResource(it)
                } ?: it.labelString ?: ""
            }
        val selectedIdx = entries?.indexOfFirst { it.value == selected }

        if (labels != null && selectedIdx != null) {
            ListDialog(
                title = item.title,
                summary = item.summary,
                labels = labels,
                selected = selectedIdx,
                onSelect = { index ->
                    item.selectItem(index)
                    viewModel.popState()
                },
                onDismissRequest = viewModel::popState,
            )
        }
    }

    @Composable
    private fun ActiveMultiListItemDialog(item : SettingsDynamicMultiList<*>) {
        val selected by item.value.observeAsState()
        val entries by item.entries.observeAsState()

        val selectedIndices = selected?.map { selItem ->
                entries?.indexOfFirst { it.value == selItem }
            }?.filterNotNull()?.filter { it >= 0 }?.toSet()
            ?: setOf()

        if (entries != null) {
            MultiListDialog(
                title = item.title,
                summary = item.summary,
                numItems = entries?.size ?: 0,
                getItem = { index ->
                    entries?.get(index)?.let { entry ->
                        val label = entry.labelId?.let {
                            stringResource(it)
                        } ?: entry.labelString ?: ""
                        Text(label)
                    }
                },
                selected = selectedIndices,
                onSelect = { indices ->
                    item.selectItems(indices)
                    viewModel.popState()
                },
                onDismissRequest = viewModel::popState,
            )
        }
    }

    @Composable
    private fun ActiveStringItemDialog(item : SettingsString) {
        val value by item.value.observeAsState()
        StringDialog(
            title = item.title,
            summary = item.dialogMsg ?: item.summary,
            hint = item.hint,
            value = value,
            onValueChange = { value ->
                item.setValue(value)
                viewModel.popState()
            },
            onDismissRequest = viewModel::popState,
        )
    }

    @Composable
    private fun ActiveIntegerItemDialog(item : SettingsInteger) {
        val value by item.value.observeAsState()
        StringDialog(
            title = item.title,
            summary = item.summary,
            hint = item.hint,
            value = value.toString(),
            onValueChange = { value ->
                try {
                    item.setValue(value.toInt())
                } catch (e : NumberFormatException) {
                    // ignore, should never happen
                    // not so bad to ignore if does
                }
                viewModel.popState()
            },
            onDismissRequest = viewModel::popState,
            keyboardType = KeyboardType.Number,
        )
    }

    private fun handleBack() {
        if (viewModel.state.value.hasBackStack)
            viewModel.popState()
        else
            finish()
    }

    private fun appExit() {
        ExitActivity.exit(getApplication());
    }

    private fun openHTMLPage(url : String) {
        startActivity(Intent(
            Intent.ACTION_VIEW,
            url.toUri(),
            this,
            HTMLActivity::class.java
        ))
    }
}

