package invalid.lena.rsend

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.Spinner
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat
import java.io.File

// FolderEditActivity adds or edits one folder mapping. index < 0 means a new
// folder.
class FolderEditActivity : AppCompatActivity() {

    private var index: Int = -1
    private lateinit var name: EditText
    private lateinit var local: EditText
    private lateinit var remoteName: Spinner
    private lateinit var remotePath: EditText
    private lateinit var excludes: EditText
    private lateinit var delete: SwitchCompat
    private var mirrorConfirmed = false

    // remoteNames[i] is the real name behind spinner position i.
    private var remoteNames: List<String> = emptyList()

    // The picker returns a real filesystem path; drop it into the local field and
    // name the folder after its basename if the name is still blank.
    private val pickFolder =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val path = result.data?.getStringExtra("path") ?: return@registerForActivityResult
                local.setText(path)
                if (name.text.isNullOrBlank()) name.setText(File(path).name)
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_folder)
        setSupportActionBar(findViewById(R.id.toolbar))
        title = "Folder"
        fitSystemBars()
        name = findViewById(R.id.name)
        local = findViewById(R.id.local)
        remoteName = findViewById(R.id.remoteName)
        remotePath = findViewById(R.id.remotePath)
        excludes = findViewById(R.id.excludes)
        delete = findViewById(R.id.delete)

        index = intent.getIntExtra("index", -1)
        val cfg = Config.load(this)
        val f = if (index in cfg.folders.indices) cfg.folders[index] else Folder()
        name.setText(f.name)
        local.setText(f.local)
        remotePath.setText(f.remotePath)
        excludes.setText(f.excludes.joinToString(", "))
        delete.isChecked = f.delete

        val names = cfg.remotes.map { it.name }.toMutableList()
        remoteNames = names
        remoteName.adapter =
            ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, names)
        val sel = names.indexOf(f.remoteName)
        if (sel >= 0) remoteName.setSelection(sel)

        findViewById<Button>(R.id.browse).setOnClickListener {
            pickFolder.launch(
                Intent(this, FolderPickerActivity::class.java)
                    .putExtra("start", local.text.toString().trim())
            )
        }

        findViewById<Button>(R.id.save).setOnClickListener { if (saveFolder()) finish() }
        val del = findViewById<Button>(R.id.removeFolder)
        del.visibility = if (index < 0) View.GONE else View.VISIBLE
        del.setOnClickListener { removeFolder(); finish() }
    }

    private fun saveFolder(): Boolean {
        val pos = remoteName.selectedItemPosition
        if (pos !in remoteNames.indices) {
            Toast.makeText(this, "Add a remote first.", Toast.LENGTH_LONG).show()
            return false
        }
        val f = Folder(
            name = name.text.toString().trim(),
            local = local.text.toString().trim(),
            remoteName = remoteNames[pos],
            remotePath = remotePath.text.toString().trim(),
            delete = delete.isChecked,
            excludes = excludes.text.toString().split(",").map { it.trim() }.filter { it.isNotEmpty() },
        )
        // Neither filesystem root is a reasonable backup boundary. Require a
        // local path below root and a named remote destination tree.
        if (f.local.isEmpty() || f.remotePath.isEmpty()) {
            Toast.makeText(this, "Set both local and remote paths first.", Toast.LENGTH_LONG).show()
            return false
        }
        if (!FolderRules.nameAllowed(f.name) || !FolderRules.remotePathAllowed(f.remotePath) ||
            !FolderRules.excludesAllowed(f.excludes)
        ) {
            Toast.makeText(
                this,
                "Names, remote paths, and excludes cannot contain control characters; " +
                    "use a named remote directory without :, ., .., or a bare ~.",
                Toast.LENGTH_LONG,
            ).show()
            return false
        }
        if (!FolderRules.localPathAllowed(f.local)) {
            Toast.makeText(this, "Local path must be an absolute path below the filesystem root.", Toast.LENGTH_LONG)
                .show()
            return false
        }
        val localFile = File(f.local)
        if (localFile.exists() && !localFile.isDirectory) {
            Toast.makeText(this, "Local path is not a directory.", Toast.LENGTH_LONG).show()
            return false
        }
        if (localFile.exists() && !FolderRules.sourceReadable(f.local)) {
            Toast.makeText(this, "Local directory cannot be read.", Toast.LENGTH_LONG).show()
            return false
        }
        if (!localFile.exists()) {
            Toast.makeText(this, "Warning: local path does not exist yet.", Toast.LENGTH_LONG).show()
        }
        val cfg = Config.load(this)
        val conflict = FolderRules.destinationConflict(cfg, f, index)
        if (conflict != null) {
            val label = conflict.name.ifEmpty { conflict.remotePath }
            Toast.makeText(
                this,
                "Remote destination conflicts with folder \"$label\". Use separate trees and one path style per remote.",
                Toast.LENGTH_LONG,
            ).show()
            return false
        }
        val oldMirror = cfg.folders.getOrNull(index)?.delete == true
        if (f.delete && !oldMirror && !mirrorConfirmed) {
            AlertDialog.Builder(this)
                .setTitle("Enable exact mirror?")
                .setMessage(
                    "Mirror follows rsync exactly: files absent locally are deleted remotely. " +
                        "An empty local folder empties the remote destination."
                )
                .setPositiveButton("Enable mirror") { _, _ ->
                    mirrorConfirmed = true
                    if (saveFolder()) finish()
                }
                .setNegativeButton("Cancel", null)
                .show()
            return false
        }
        val list = cfg.folders.toMutableList()
        if (index in list.indices) list[index] = f else list.add(f)
        Config.save(this, cfg.copy(folders = list))
        return true
    }

    private fun removeFolder() {
        val cfg = Config.load(this)
        val list = cfg.folders.toMutableList()
        if (index in list.indices) {
            list.removeAt(index)
            Config.save(this, cfg.copy(folders = list))
        }
    }
}
