package com.syncflow.data.providers.sftp

import com.syncflow.data.providers.CloudProvider
import com.syncflow.data.security.CredentialStore
import com.syncflow.domain.model.CloudAccount
import com.syncflow.domain.model.RemoteFile
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.sftp.SFTPClient
import net.schmizz.sshj.xfer.InMemorySourceFile
import java.io.InputStream
import java.io.OutputStream
import java.time.Instant

class SftpProvider(private val account: CloudAccount, private val credentialStore: CredentialStore) : CloudProvider {

    private val creds = Json.parseToJsonElement(account.credentialJson).jsonObject
    private val host = account.serverUrl ?: "localhost"
    private val port = account.port ?: 22
    private val username = creds["username"]?.jsonPrimitive?.content ?: ""
    private val password = creds["password"]?.jsonPrimitive?.content
    private val privateKey = creds["private_key"]?.jsonPrimitive?.content

    private fun <T> withSftp(block: (SFTPClient) -> T): T {
        val ssh = SSHClient()
        ssh.addHostKeyVerifier(TofuHostKeyVerifier(credentialStore))
        ssh.connect(host, port)
        try {
            if (!privateKey.isNullOrBlank()) {
                ssh.authPublickey(username, ssh.loadKeys(privateKey, null, null))
            } else {
                ssh.authPassword(username, password ?: "")
            }
            return ssh.newSFTPClient().use(block)
        } finally {
            ssh.disconnect()
        }
    }

    override suspend fun testConnection(): Result<Unit> = runCatching { withSftp { it.ls("/") } }

    override suspend fun listFiles(remotePath: String): Result<List<RemoteFile>> = runCatching {
        withSftp { sftp ->
            sftp.ls(remotePath).map { entry ->
                RemoteFile(
                    path = "$remotePath/${entry.name}".replace("//", "/"),
                    name = entry.name,
                    isDirectory = entry.isDirectory,
                    sizeBytes = entry.attributes.size,
                    modifiedAt = Instant.ofEpochSecond(entry.attributes.mtime.toLong()),
                    etag = null,
                    mimeType = null,
                )
            }
        }
    }

    override suspend fun uploadFile(
        localStream: InputStream,
        remotePath: String,
        sizeBytes: Long,
        onProgress: (Long) -> Unit,
    ): Result<RemoteFile> = runCatching {
        withSftp { sftp ->
            sftp.put(object : InMemorySourceFile() {
                override fun getName() = remotePath.substringAfterLast('/')
                override fun getLength() = sizeBytes
                override fun getInputStream() = localStream
            }, remotePath)
        }
        getFileMetadata(remotePath).getOrThrow()
    }

    override suspend fun downloadFile(remotePath: String, destination: OutputStream, onProgress: (Long) -> Unit): Result<Unit> = runCatching {
        val tmp = java.io.File.createTempFile("sf_", ".tmp")
        try {
            withSftp { sftp -> sftp.get(remotePath, tmp.absolutePath) }
            var total = 0L
            tmp.inputStream().use { src ->
                val buf = ByteArray(65536)
                var n: Int
                while (src.read(buf).also { n = it } != -1) { destination.write(buf, 0, n); total += n; onProgress(total) }
            }
        } finally {
            tmp.delete()
        }
    }

    override suspend fun deleteFile(remotePath: String): Result<Unit> = runCatching {
        withSftp { it.rm(remotePath) }
    }

    override suspend fun createDirectory(remotePath: String): Result<Unit> = runCatching {
        withSftp { it.mkdirs(remotePath) }
    }

    override suspend fun getFileMetadata(remotePath: String): Result<RemoteFile> = runCatching {
        withSftp { sftp ->
            val attr = sftp.stat(remotePath)
            RemoteFile(
                path = remotePath,
                name = remotePath.substringAfterLast('/'),
                isDirectory = attr.type.toString().contains("DIRECTORY"),
                sizeBytes = attr.size,
                modifiedAt = Instant.ofEpochSecond(attr.mtime.toLong()),
                etag = null,
                mimeType = null,
            )
        }
    }

    override suspend fun moveFile(fromPath: String, toPath: String): Result<Unit> = runCatching {
        withSftp { it.rename(fromPath, toPath) }
    }
}
