/*
 * Copyright (c) 2022 New Vector Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package im.vector.app.features.onboarding

import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.auth.registration.RegistrationResult.FlowResponse
import org.matrix.android.sdk.api.auth.registration.RegistrationResult.Success
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.failure.is401
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
import org.matrix.android.sdk.api.auth.registration.RegistrationResult as MatrixRegistrationResult

class RegistrationActionHandler @Inject constructor() {

    suspend fun handleRegisterAction(registrationWizard: RegistrationWizard, action: RegisterAction): RegistrationResult {
        return when (action) {
            RegisterAction.StartRegistration -> resultOf { registrationWizard.getRegistrationFlow() }
            is RegisterAction.CaptchaDone -> resultOf { registrationWizard.performReCaptcha(action.captchaResponse) }
            is RegisterAction.AcceptTerms -> resultOf { registrationWizard.acceptTerms() }
            is RegisterAction.RegisterDummy -> resultOf { registrationWizard.dummy() }
            is RegisterAction.AddThreePid -> handleAddThreePid(registrationWizard, action)
            is RegisterAction.SendAgainThreePid -> resultOf { registrationWizard.sendAgainThreePid() }
            is RegisterAction.ValidateThreePid -> resultOf { registrationWizard.handleValidateThreePid(action.code) }
            is RegisterAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailIsValidated(registrationWizard, action.delayMillis)
            is RegisterAction.CreateAccount -> resultOf {
                registrationWizard.createAccount(
                        action.username,
                        action.password,
                        action.initialDeviceName
                )
            }
        }
    }

    private suspend fun handleAddThreePid(wizard: RegistrationWizard, action: RegisterAction.AddThreePid): RegistrationResult {
        return runCatching { wizard.addThreePid(action.threePid) }.fold(
                onSuccess = { it.toRegistrationResult() },
                onFailure = {
                    when {
                        action.threePid is RegisterThreePid.Email && it.is401() -> RegistrationResult.SendEmailSuccess(action.threePid.email)
                        else -> RegistrationResult.Error(it)
                    }
                }
        )
    }

    private tailrec suspend fun handleCheckIfEmailIsValidated(registrationWizard: RegistrationWizard, delayMillis: Long): RegistrationResult {
        return runCatching { registrationWizard.checkIfEmailHasBeenValidated(delayMillis) }.fold(
                onSuccess = { it.toRegistrationResult() },
                onFailure = {
                    when {
                        it.is401() -> null // recursively continue to check with a delay
                        else -> RegistrationResult.Error(it)
                    }
                }
        ) ?: handleCheckIfEmailIsValidated(registrationWizard, 10_000)
    }
}

private inline fun resultOf(block: () -> MatrixRegistrationResult): RegistrationResult {
    return runCatching { block() }.fold(
            onSuccess = { it.toRegistrationResult() },
            onFailure = { RegistrationResult.Error(it) }
    )
}

private fun MatrixRegistrationResult.toRegistrationResult() = when (this) {
    is FlowResponse -> RegistrationResult.NextStep(flowResult)
    is Success -> RegistrationResult.Complete(session)
}

sealed interface RegistrationResult {
    data class Error(val cause: Throwable) : RegistrationResult
    data class Complete(val session: Session) : RegistrationResult
    data class NextStep(val flowResult: FlowResult) : RegistrationResult
    data class SendEmailSuccess(val email: String) : RegistrationResult
}

sealed interface RegisterAction {
    object StartRegistration : RegisterAction
    data class CreateAccount(val username: String, val password: String, val initialDeviceName: String) : RegisterAction

    data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction
    object SendAgainThreePid : RegisterAction

    // TODO Confirm Email (from link in the email, open in the phone, intercepted by the app)
    data class ValidateThreePid(val code: String) : RegisterAction

    data class CheckIfEmailHasBeenValidated(val delayMillis: Long) : RegisterAction

    data class CaptchaDone(val captchaResponse: String) : RegisterAction
    object AcceptTerms : RegisterAction
    object RegisterDummy : RegisterAction
}

fun RegisterAction.ignoresResult() = when (this) {
    is RegisterAction.SendAgainThreePid -> true
    else -> false
}

fun RegisterAction.hasLoadingState() = when (this) {
    is RegisterAction.CheckIfEmailHasBeenValidated -> false
    else -> true
}
