package nerd.tuxmobil.fahrplan.congress.schedule

import android.app.KeyguardManager
import android.app.ProgressDialog
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.activity.viewModels
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import androidx.appcompat.widget.Toolbar
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import androidx.core.view.MenuProvider
import androidx.core.view.isVisible
import androidx.core.widget.ContentLoadingProgressBar
import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener
import androidx.lifecycle.Lifecycle.State.RESUMED
import info.metadude.android.eventfahrplan.commons.flow.observe
import info.metadude.android.eventfahrplan.commons.logging.Logging
import nerd.tuxmobil.fahrplan.congress.R
import nerd.tuxmobil.fahrplan.congress.about.AboutDialog
import nerd.tuxmobil.fahrplan.congress.alarms.AlarmsActivity
import nerd.tuxmobil.fahrplan.congress.alarms.AlarmsFragment
import nerd.tuxmobil.fahrplan.congress.base.AbstractListFragment.OnSessionListClick
import nerd.tuxmobil.fahrplan.congress.base.BaseActivity
import nerd.tuxmobil.fahrplan.congress.base.OnSessionItemClickListener
import nerd.tuxmobil.fahrplan.congress.changes.ChangeListActivity
import nerd.tuxmobil.fahrplan.congress.changes.ChangeListFragment
import nerd.tuxmobil.fahrplan.congress.changes.statistic.ChangeStatisticsUiState
import nerd.tuxmobil.fahrplan.congress.changes.statistic.ChangeStatisticScreen
import nerd.tuxmobil.fahrplan.congress.contract.BundleKeys
import nerd.tuxmobil.fahrplan.congress.contract.BundleKeys.SCHEDULE_UPDATE_NOTIFICATION
import nerd.tuxmobil.fahrplan.congress.designsystem.themes.EventFahrplanTheme
import nerd.tuxmobil.fahrplan.congress.details.SessionDetailsActivity
import nerd.tuxmobil.fahrplan.congress.details.SessionDetailsFragment
import nerd.tuxmobil.fahrplan.congress.engagements.initUserEngagement
import nerd.tuxmobil.fahrplan.congress.extensions.applyEdgeToEdgeInsets
import nerd.tuxmobil.fahrplan.congress.extensions.applyToolbar
import nerd.tuxmobil.fahrplan.congress.extensions.isLandscape
import nerd.tuxmobil.fahrplan.congress.extensions.withExtras
import nerd.tuxmobil.fahrplan.congress.favorites.StarredListActivity
import nerd.tuxmobil.fahrplan.congress.favorites.StarredListFragment
import nerd.tuxmobil.fahrplan.congress.net.errors.ErrorMessage
import nerd.tuxmobil.fahrplan.congress.net.errors.ErrorMessage.TitledMessage
import nerd.tuxmobil.fahrplan.congress.net.errors.ErrorMessageScreen
import nerd.tuxmobil.fahrplan.congress.notifications.NotificationHelper
import nerd.tuxmobil.fahrplan.congress.reporting.TraceDroidEmailSender
import nerd.tuxmobil.fahrplan.congress.repositories.AppRepository
import nerd.tuxmobil.fahrplan.congress.schedule.FahrplanFragment.OnSessionClickListener
import nerd.tuxmobil.fahrplan.congress.schedule.observables.LoadScheduleUiState
import nerd.tuxmobil.fahrplan.congress.search.SearchActivity
import nerd.tuxmobil.fahrplan.congress.search.SearchFragment
import nerd.tuxmobil.fahrplan.congress.settings.SettingsActivity
import nerd.tuxmobil.fahrplan.congress.sidepane.OnSidePaneCloseListener
import nerd.tuxmobil.fahrplan.congress.utils.ConfirmationDialog.OnConfirmationDialogClicked
import nerd.tuxmobil.fahrplan.congress.utils.setShowWhenLockedCompat

class MainActivity : BaseActivity(),
    MenuProvider,
    OnSidePaneCloseListener,
    OnSessionListClick,
    OnSessionClickListener,
    OnSessionItemClickListener,
    OnBackStackChangedListener,
    OnConfirmationDialogClicked {

    companion object {

        private const val LOG_TAG = "MainActivity"
        private const val INVALID_NOTIFICATION_ID = -1

        lateinit var instance: MainActivity

        /**
         * Returns a unique intent to be used to launch this activity.
         * The given parameters [sessionId] and [dayIndex] are passed along as bundle extras.
         * The [sessionId] is also used to ensure this intent is unique by definition of
         * [Intent.filterEquals].
         */
        fun createLaunchIntent(
            context: Context,
            sessionId: String,
            dayIndex: Int,
            notificationId: Int
        ) = Intent(context, MainActivity::class.java)
            .apply {
                data = "fake://$sessionId".toUri()
                flags = FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
            }
            .withExtras(
                BundleKeys.SESSION_ALARM_SESSION_ID to sessionId,
                BundleKeys.SESSION_ALARM_DAY_INDEX to dayIndex,
                BundleKeys.SESSION_ALARM_NOTIFICATION_ID to notificationId
            )
    }

    private lateinit var notificationHelper: NotificationHelper
    private lateinit var keyguardManager: KeyguardManager
    private lateinit var errorMessageFactory: ErrorMessage.Factory
    private lateinit var progressBar: ContentLoadingProgressBar
    private val logging = Logging.get()
    private var progressDialog: ProgressDialog? = null
    private val viewModel: MainViewModel by viewModels {
        MainViewModelFactory(AppRepository, notificationHelper, errorMessageFactory)
    }

    private var isScreenLocked = false
    private var isAlarmsInSidePane = false
    private var isFavoritesInSidePane = false
    private var isSearchInSidePane = false
    private var shouldScrollToCurrent = true

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        setShowWhenLockedCompat(AppRepository.readShowOnLockscreenEnabled())
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        instance = this
        setContentView(R.layout.main)
        addMenuProvider(this, this, RESUMED)

        logging.report(LOG_TAG, buildString {
            append("MainActivity#onCreate: ")
            append("thread=${Thread.currentThread().name}, ")
            append("savedState=${savedInstanceState != null}, ")
            append("isScheduleUpdate=${intent.getBooleanExtra(SCHEDULE_UPDATE_NOTIFICATION, false)}, ")
            append("isSessionAlarm=${intent.getStringExtra(BundleKeys.SESSION_ALARM_SESSION_ID) != null}")
        })

        notificationHelper = NotificationHelper(this)

        keyguardManager = getSystemService()!!
        errorMessageFactory = ErrorMessage.Factory(this)
        val toolbar = requireViewByIdCompat<Toolbar>(R.id.toolbar)
        applyToolbar(toolbar) {
            title = if (isLandscape()) getString(R.string.app_name) else ""
            setDisplayShowHomeEnabled(true)
            setDefaultDisplayHomeAsUpEnabled(true)
        }
        progressBar = requireViewByIdCompat(R.id.progress)

        val rootLayout = requireViewByIdCompat<View>(R.id.root_layout)
        rootLayout.applyEdgeToEdgeInsets()

        TraceDroidEmailSender.sendStackTraces(this)
        resetProgressDialog()

        supportFragmentManager.addOnBackStackChangedListener(this)
        if (findViewById<View>(R.id.schedule) != null && findFragment(FahrplanFragment.FRAGMENT_TAG) == null) {
            replaceFragment(R.id.schedule, FahrplanFragment(), FahrplanFragment.FRAGMENT_TAG)
        }
        if (findViewById<View>(R.id.detail) == null) {
            removeFragment(SessionDetailsFragment.FRAGMENT_TAG)
        }
        initUserEngagement()
        observeViewModel()
        onSessionAlarmNotificationTapped(intent)
        onScheduleUpdateNotificationTapped(intent)
        viewModel.checkPostNotificationsPermission()
        window.decorView.post { handleAppLink(intent) }
    }

    private fun handleAppLink(intent: Intent) {
        if (intent.action == Intent.ACTION_VIEW) {
            intent.data?.let {
                viewModel.openSessionDetailsFromAppLink(it)
            }
        }
    }

    @Composable
    private fun ScheduleChangeStatistics(state: ChangeStatisticsUiState) {
        EventFahrplanTheme {
            ChangeStatisticScreen(
                uiState = state,
                onConfirm = { viewModel.onCloseChangeStatisticsScreen(shouldOpenSessionChanges = true) },
                onDismiss = { viewModel.onCloseChangeStatisticsScreen(shouldOpenSessionChanges = false) },
            )
        }
    }

    @Composable
    private fun ErrorMessage(errorMessage: TitledMessage) {
        EventFahrplanTheme {
            ErrorMessageScreen(
                errorMessage = errorMessage,
                onConfirm = viewModel::onCloseErrorMessageScreen,
                onDismiss = viewModel::onCloseErrorMessageScreen,
            )
        }
    }

    private fun observeViewModel() {
        viewModel.loadScheduleUiState.observe(this) {
            updateUi(it)
        }
        viewModel.errorMessage.observe(this) { errorMessage ->
            requireViewByIdCompat<ComposeView>(R.id.error_message_view).setContent {
                errorMessage?.let { ErrorMessage(it) }
            }
        }
        viewModel.simpleErrorMessageUiState.observe(this) { state ->
            state?.let {
                val duration = if (it.shouldShowLong) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
                Toast.makeText(this, it.errorMessage.message, duration).show()
            }
        }
        viewModel.changeStatisticsUiState.observe(this) { state ->
            requireViewByIdCompat<ComposeView>(R.id.schedule_changes_statistic_view).setContent {
                state?.let { ScheduleChangeStatistics(it) }
            }
        }
        viewModel.openSessionChanges.observe(this) {
            openSessionChanges()
        }
        viewModel.showAbout.observe(this) {
            showAboutDialog()
        }
        viewModel.openSessionDetails.observe(this) {
            openSessionDetails()
        }
        viewModel.missingPostNotificationsPermission.observe(this) {
            Toast.makeText(this, R.string.alarms_disabled_notifications_permission_missing, Toast.LENGTH_LONG).show()
        }
    }

    private fun updateUi(uiState: LoadScheduleUiState) {
        if (uiState is LoadScheduleUiState.Initializing) {
            showProgressDialog(uiState.progressInfo)
        } else {
            hideProgressDialog()
        }
        if (uiState is LoadScheduleUiState.Active) {
            progressBar.show()
        } else {
            progressBar.hide()
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        logging.report(LOG_TAG, buildString {
            append("MainActivity#onNewIntent: ")
            append("thread=${Thread.currentThread().name}, ")
            append("isScheduleUpdate=${intent.getBooleanExtra(SCHEDULE_UPDATE_NOTIFICATION, false)}, ")
            append("isSessionAlarm=${intent.getStringExtra(BundleKeys.SESSION_ALARM_SESSION_ID) != null}")
        })
        onSessionAlarmNotificationTapped(intent)
        onScheduleUpdateNotificationTapped(intent)
        handleAppLink(intent)
    }

    private fun onSessionAlarmNotificationTapped(intent: Intent) {
        val notificationId = intent.getIntExtra(BundleKeys.SESSION_ALARM_NOTIFICATION_ID, INVALID_NOTIFICATION_ID)
        if (notificationId != INVALID_NOTIFICATION_ID) {
            logging.report(LOG_TAG, "Tapped session alarm notification.")
            viewModel.deleteSessionAlarmNotificationId(notificationId)
        }
    }

    private fun onScheduleUpdateNotificationTapped(intent: Intent) {
        val isScheduleUpdateNotification = intent.getBooleanExtra(SCHEDULE_UPDATE_NOTIFICATION, false)
        if (isScheduleUpdateNotification) {
            logging.report(LOG_TAG, "Tapped schedule update notification.")
            intent.removeExtra(SCHEDULE_UPDATE_NOTIFICATION)
        }
    }

    override fun onDestroy() {
        viewModel.cancelLoading()
        hideProgressDialog()
        super.onDestroy()
    }

    override fun onResume() {
        super.onResume()
        isScreenLocked = keyguardManager.isKeyguardLocked
        val sidePaneView = findViewById<FragmentContainerView>(R.id.detail)
        if (sidePaneView != null && isAlarmsInSidePane) {
            sidePaneView.isVisible = !isScreenLocked
        }
        if (sidePaneView != null && isFavoritesInSidePane) {
            sidePaneView.isVisible = !isScreenLocked
        }
    }

    private fun showAboutDialog() {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.addToBackStack(null)
        AboutDialog().show(transaction, AboutDialog.FRAGMENT_TAG)
    }

    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        menuInflater.inflate(R.menu.mainmenu, menu)
    }

    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        when (menuItem.itemId) {
            R.id.menu_item_about -> viewModel.showAboutDialog()
            R.id.menu_item_alarms -> openAlarms()
            R.id.menu_item_settings -> SettingsActivity.startForResult(this)
            R.id.menu_item_search -> openSearch()
            R.id.menu_item_schedule_changes -> openSessionChanges()
            R.id.menu_item_favorites -> openFavorites()
            else -> return false
        }
        return true
    }

    private fun openSessionDetails() {
        val sidePaneView = findViewById<FragmentContainerView>(R.id.detail)
        if (sidePaneView == null) {
            SessionDetailsActivity.startForResult(this)
        } else {
            SessionDetailsFragment.replaceAtBackStack(supportFragmentManager, R.id.detail, true)
        }
    }

    override fun onSidePaneClose(fragmentTag: String) {
        findViewById<View>(R.id.detail)?.let {
            it.isVisible = false
        }
        if (fragmentTag == AlarmsFragment.FRAGMENT_TAG) {
            isAlarmsInSidePane = false
        }
        if (fragmentTag == StarredListFragment.FRAGMENT_TAG) {
            isFavoritesInSidePane = false
        }
        if (fragmentTag == SearchFragment.FRAGMENT_TAG) {
            isSearchInSidePane = false
        }
        removeFragment(fragmentTag)
    }

    public override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        super.onActivityResult(requestCode, resultCode, intent)
        when (requestCode) {

            SessionDetailsActivity.REQUEST_CODE ->
                if (resultCode == RESULT_CANCELED) {
                    shouldScrollToCurrent = false
                }

            SettingsActivity.REQUEST_CODE ->
                if (resultCode == RESULT_OK && intent != null) {
                    val isAlternativeHighlightingUpdated = intent.getBooleanExtra(
                        BundleKeys.ALTERNATIVE_HIGHLIGHTING_UPDATED, false)
                    val isUseDeviceTimeZoneUpdated = intent.getBooleanExtra(
                        BundleKeys.USE_DEVICE_TIME_ZONE_UPDATED, false)

                    @Suppress("kotlin:S1066")
                    if (isAlternativeHighlightingUpdated || isUseDeviceTimeZoneUpdated) {
                        if (findViewById<View>(R.id.schedule) != null && findFragment(FahrplanFragment.FRAGMENT_TAG) != null) {
                            replaceFragment(R.id.schedule, FahrplanFragment(), FahrplanFragment.FRAGMENT_TAG)
                        }
                    }
                    var shouldFetchFahrplan = false
                    val isScheduleUrlUpdated = resources.getBoolean(
                        R.bool.bundle_key_schedule_url_updated_default_value)
                    if (intent.getBooleanExtra(BundleKeys.SCHEDULE_URL_UPDATED, isScheduleUrlUpdated)) {
                        shouldFetchFahrplan = true
                    }
                    val isEngelsystemShiftsUrlUpdated = resources.getBoolean(
                        R.bool.bundle_key_engelsystem_shifts_url_updated_default_value)
                    if (intent.getBooleanExtra(BundleKeys.ENGELSYSTEM_SHIFTS_URL_UPDATED, isEngelsystemShiftsUrlUpdated)) {
                        shouldFetchFahrplan = true
                    }
                    if (shouldFetchFahrplan) {
                        // TODO Handle schedule update in AppRepository; above code becomes needless
                        viewModel.requestScheduleUpdate(isUserRequest = true)
                    }
                    val isShowOnLockscreenUpdated = resources.getBoolean(
                        R.bool.bundle_key_show_on_lockscreen_updated_default_value)
                    if (intent.getBooleanExtra(BundleKeys.SHOW_ON_LOCKSCREEN_UPDATED, isShowOnLockscreenUpdated)) {
                        setShowWhenLockedCompat(AppRepository.readShowOnLockscreenEnabled())
                    }
                }
        }
    }

    override fun onSessionListClick(sessionId: String) {
        viewModel.openSessionDetails(sessionId)
    }

    override fun onSessionClick(sessionId: String) {
        viewModel.openSessionDetails(sessionId)
    }

    override fun onBackStackChanged() {
        toggleSidePaneViewVisibility(supportFragmentManager, R.id.detail)
        invalidateOptionsMenu()
    }

    private fun showProgressDialog(@StringRes message: Int) {
        hideProgressDialog()
        progressDialog = ProgressDialog.show(this, "", resources.getString(message), true)
    }

    private fun hideProgressDialog() {
        progressDialog?.let {
            it.dismiss()
            resetProgressDialog()
        }
    }

    private fun resetProgressDialog() {
        progressDialog = null
    }

    private fun toggleSidePaneViewVisibility(fragmentManager: FragmentManager, @IdRes detailView: Int) {
        val fragment = fragmentManager.findFragmentById(detailView)
        val hasFragment = fragment != null
        findViewById<View>(detailView)?.let { view ->
            isAlarmsInSidePane = hasFragment && fragment is AlarmsFragment
            isFavoritesInSidePane = hasFragment && fragment is StarredListFragment
            isSearchInSidePane = hasFragment && fragment is SearchFragment
            view.isVisible = (!isAlarmsInSidePane || !isFavoritesInSidePane || !isScreenLocked || !isSearchInSidePane) && hasFragment
        }
    }

    private fun openAlarms() {
        val sidePaneView = findViewById<FragmentContainerView>(R.id.detail)
        if (sidePaneView == null) {
            AlarmsActivity.start(this)
        } else if (!isScreenLocked) {
            sidePaneView.isVisible = true
            isAlarmsInSidePane = true
            AlarmsFragment.replaceAtBackStack(supportFragmentManager, R.id.detail, true)
        }
    }

    private fun openFavorites() {
        val sidePaneView = findViewById<FragmentContainerView>(R.id.detail)
        if (sidePaneView == null) {
            StarredListActivity.start(this)
        } else if (!isScreenLocked) {
            sidePaneView.isVisible = true
            isFavoritesInSidePane = true
            StarredListFragment.replaceAtBackStack(supportFragmentManager, R.id.detail, true)
        }
    }

    private fun openSessionChanges() {
        val sidePaneView = findViewById<FragmentContainerView>(R.id.detail)
        if (sidePaneView == null) {
            ChangeListActivity.start(this)
        } else {
            sidePaneView.isVisible = true
            ChangeListFragment.replaceAtBackStack(supportFragmentManager, R.id.detail, true)
        }
    }

    private fun openSearch() {
        val sidePaneView = findViewById<FragmentContainerView>(R.id.detail)
        if (sidePaneView == null) {
            SearchActivity.start(this)
        } else {
            sidePaneView.isVisible = true
            isSearchInSidePane = true
            SearchFragment.replaceAtBackStack(supportFragmentManager, R.id.detail)
        }
    }

    override fun onAccepted(requestCode: Int) {
        if (requestCode == StarredListFragment.DELETE_ALL_FAVORITES_REQUEST_CODE) {
            findFragment(StarredListFragment.FRAGMENT_TAG)?.let {
                (it as StarredListFragment).deleteAllFavorites()
            }
        }
    }

    fun shouldScheduleScrollToCurrentTimeSlot(onShouldScrollToCurrent: () -> Unit) {
        if (shouldScrollToCurrent) {
            onShouldScrollToCurrent()
        }
        shouldScrollToCurrent = true
    }

    override fun onSessionItemClick(sessionId: String) {
        viewModel.openSessionDetails(sessionId)
    }
}
