/*
 * This file is part of LibEuFin.
 * Copyright (C) 2023-2024 Taler Systems S.A.

 * LibEuFin is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3, or
 * (at your option) any later version.

 * LibEuFin is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
 * Public License for more details.

 * You should have received a copy of the GNU Affero General Public
 * License along with LibEuFin; see the file COPYING.  If not, see
 * <http://www.gnu.org/licenses/>
 */

package tech.libeufin.bank.db

import tech.libeufin.bank.BearerToken
import tech.libeufin.bank.TokenInfo
import tech.libeufin.bank.TokenScope
import tech.libeufin.common.PageParams
import tech.libeufin.common.asInstant
import tech.libeufin.common.db.*
import tech.libeufin.common.micros
import java.time.Instant

/** Data access logic for auth tokens */
class TokenDAO(private val db: Database) {
    /** Result status of token creation */
    sealed interface TokenCreationResult {
        data object Success: TokenCreationResult
        data object TanRequired: TokenCreationResult
    }

    /** Create new token for [username] */
    suspend fun create(
        username: String,
        content: ByteArray,
        creationTime: Instant,
        expirationTime: Instant,
        scope: TokenScope,
        isRefreshable: Boolean,
        description: String?,
        is2fa: Boolean
    ): TokenCreationResult = db.serializable(
        """
        SELECT out_tan_required FROM create_token(
            ?,?,?,?,?::token_scope_enum,?,?,?
        )
        """
    ) {
        setString(1, username)
        setBytes(2, content)
        setLong(3, creationTime.micros())
        setLong(4, expirationTime.micros())
        setString(5, scope.name)
        setBoolean(6, isRefreshable)
        setString(7, description)
        setBoolean(8, is2fa)
        one {
            when {
                it.getBoolean("out_tan_required") -> TokenCreationResult.TanRequired
                else -> TokenCreationResult.Success
            }
        }
    }
    
    /** Get info for [token] */
    suspend fun access(token: ByteArray, accessTime: Instant): BearerToken? = db.serializable(
        """
        UPDATE bearer_tokens
            SET last_access=?
        FROM customers
        WHERE bank_customer=customer_id AND content=? AND deleted_at IS NULL
        RETURNING
            creation_time,
            expiration_time,
            username,
            scope,
            is_refreshable
        """
    ) {
        setLong(1, accessTime.micros())
        setBytes(2, token) 
        oneOrNull {
            BearerToken(
                creationTime = it.getLong("creation_time").asInstant(),
                expirationTime = it.getLong("expiration_time").asInstant(),
                username = it.getString("username"),
                scope = it.getEnum("scope"),
                isRefreshable = it.getBoolean("is_refreshable")
            )
        }
    }
    
    /** Delete token [token] */
    suspend fun delete(token: ByteArray) = db.serializable(
        "DELETE FROM bearer_tokens WHERE content = ?"
    ) {
        setBytes(1, token)
        execute()
    }

    /** Get a page of all tokens of [username] accounts */
    suspend fun page(params: PageParams, username: String): List<TokenInfo>
        = db.page(
            params,
            "bearer_token_id",
            """
            SELECT
              creation_time,
              expiration_time,
              scope,
              is_refreshable,
              description,
              last_access,
              bearer_token_id
              FROM bearer_tokens 
              WHERE 
                bank_customer=(SELECT customer_id FROM customers WHERE deleted_at IS NULL AND username = ?) 
            AND
            """,
            {
                setString(1, username)
                1
            }
        ) {
            TokenInfo(
                creation_time = it.getTalerTimestamp("creation_time"),
                expiration = it.getTalerTimestamp("expiration_time"),
                scope = it.getEnum("scope"),
                isRefreshable = it.getBoolean("is_refreshable"),
                description = it.getString("description"),
                last_access = it.getTalerTimestamp("last_access"),
                row_id = it.getLong("bearer_token_id")
            )
        }
}