package cy.hexor.furumi.data.repository

import cy.hexor.furumi.data.local.AuthSessionStorage
import cy.hexor.furumi.data.remote.AuthApiErrorParser
import cy.hexor.furumi.data.remote.AuthEndpoints
import cy.hexor.furumi.data.remote.api.AuthApi
import cy.hexor.furumi.data.remote.model.LoginRequest
import cy.hexor.furumi.data.remote.model.LoginResponse
import cy.hexor.furumi.data.remote.model.LogoutRequest
import cy.hexor.furumi.data.remote.model.SsoExchangeRequest
import cy.hexor.furumi.data.remote.model.toDomainSession
import cy.hexor.furumi.domain.model.AuthException
import cy.hexor.furumi.domain.model.AuthSession
import cy.hexor.furumi.domain.model.ServerConfig
import cy.hexor.furumi.domain.repository.AuthRepository
import kotlinx.coroutines.CancellationException
import retrofit2.Response
import javax.inject.Inject

class AuthRepositoryImpl @Inject constructor(
    private val authApi: AuthApi,
    private val sessionStorage: AuthSessionStorage,
    private val authEndpoints: AuthEndpoints,
    private val errorParser: AuthApiErrorParser,
    private val deviceInfo: AuthDeviceInfo
) : AuthRepository {

    override suspend fun login(config: ServerConfig, password: String): Result<AuthSession> {
        return authResult {
            val normalizedConfig = ServerConfig.credentials(config.baseUrl, config.login).getOrThrow()
            val response = authApi.login(
                url = authEndpoints.passwordLogin(normalizedConfig.baseUrl),
                request = LoginRequest(
                    username = normalizedConfig.login,
                    password = password,
                    deviceName = deviceInfo.deviceName
                )
            )
            saveAuthSession(response, normalizedConfig.baseUrl)
        }
    }

    override suspend fun createSsoAuthorizationUrl(serverUrl: String): Result<String> {
        return authResult {
            val config = ServerConfig.sso(serverUrl).getOrThrow()
            sessionStorage.savePendingSsoBaseUrl(config.baseUrl)
            authEndpoints.ssoStart(config.baseUrl)
        }
    }

    override suspend fun completeSsoLogin(callbackUrl: String): Result<AuthSession> {
        return authResult {
            val callback = authEndpoints.parseSsoCallback(callbackUrl).getOrThrow()
            val baseUrl = sessionStorage.getPendingSsoBaseUrl()
                ?: sessionStorage.getBaseUrl()
                ?: throw AuthException("SSO server is missing. Start SSO login again.")
            val response = authApi.ssoExchange(
                url = authEndpoints.ssoExchange(baseUrl),
                request = SsoExchangeRequest(
                    code = callback.code,
                    deviceName = deviceInfo.deviceName
                )
            )
            saveAuthSession(response, baseUrl)
        }
    }

    private fun saveAuthSession(response: Response<LoginResponse>, baseUrl: String): AuthSession {
        if (!response.isSuccessful) {
            throw AuthException(errorParser.messageFrom(response))
        }

        val body = response.body() ?: throw AuthException("Auth response is empty")
        val session = body.toDomainSession(
            serverBaseUrl = baseUrl,
            receivedAtEpochSeconds = nowEpochSeconds()
        )
        sessionStorage.saveSession(session)
        return session
    }

    override suspend fun logout(): Result<Unit> {
        return try {
            val session = sessionStorage.getSession()
            if (session != null) {
                authApi.logout(
                    url = authEndpoints.logout(session.serverBaseUrl),
                    bearerToken = session.tokens.authorizationHeader,
                    request = LogoutRequest(session.tokens.refreshToken)
                )
            }
            Result.success(Unit)
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(AuthException("Logged out locally, but server logout failed", e))
        } finally {
            sessionStorage.clearSession()
        }
    }

    override fun getCurrentSession(): AuthSession? {
        return sessionStorage.getSession()
    }

    override fun getSavedServerUrl(): String? {
        return sessionStorage.getBaseUrl() ?: sessionStorage.getPendingSsoBaseUrl()
    }

    private suspend fun <T> authResult(block: suspend () -> T): Result<T> {
        return try {
            Result.success(block())
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    private fun nowEpochSeconds(): Long {
        return System.currentTimeMillis() / 1000L
    }
}
