package ru.forbion.f7cloud.feature.tasks

import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.auth.OcsUserResolver
import ru.forbion.f7cloud.core.network.CalDavClient
import ru.forbion.f7cloud.core.network.DavCalendar
import ru.forbion.f7cloud.core.network.DavTask
import ru.forbion.f7cloud.core.network.NetworkFactory
import java.net.URLDecoder
import java.time.LocalDate
import java.time.format.DateTimeFormatter

data class TaskListItem(
    val href: String,
    val displayName: String,
    val slug: String,
)

data class TaskItem(
    val uid: String,
    val href: String,
    val etag: String,
    val summary: String,
    val status: String,
    val due: String,
    val dueRaw: String,
    val priority: Int,
    val percentComplete: Int,
    val description: String,
    val listHref: String,
    val listName: String,
) {
    val isCompleted: Boolean
        get() = status.equals("COMPLETED", ignoreCase = true) || percentComplete >= 100

    fun toDavTask(): DavTask = DavTask(
        uid = uid,
        href = href,
        etag = etag,
        summary = summary,
        status = status,
        due = due,
        dueRaw = dueRaw,
        priority = priority,
        percentComplete = percentComplete,
        description = description,
        calendarName = listName,
        calendarHref = listHref,
    )
}

class TasksRepository {
    fun listTaskLists(session: AuthSession): List<TaskListItem> {
        val client = authedClient(session)
        val userId = davUserId(session)
        val base = CalDavClient.calendarsBase(session.serverUrl, userId)
        return CalDavClient.listCalendars(client, base)
            .map { cal ->
                TaskListItem(
                    href = cal.href,
                    displayName = cal.displayName.ifBlank { calendarSlug(cal.href) },
                    slug = calendarSlug(cal.href),
                )
            }
            .sortedBy { it.displayName.lowercase() }
    }

    fun loadTasks(session: AuthSession, listHref: String): List<TaskItem> {
        val client = authedClient(session)
        val calendar = calendarForHref(session, listHref)
        return CalDavClient.queryTasks(client, calendar)
            .map { it.toTaskItem() }
            .sortedWith(taskComparator)
    }

    fun createTask(
        session: AuthSession,
        listHref: String,
        summary: String,
        dueDate: LocalDate? = null,
        priority: Int = 0,
        description: String = "",
    ): TaskItem {
        val client = authedClient(session)
        val calendar = calendarForHref(session, listHref)
        return CalDavClient.createTask(
            client = client,
            calendar = calendar,
            summary = summary,
            dueDate = dueDate,
            priority = priority,
            description = description,
        ).toTaskItem()
    }

    fun toggleComplete(session: AuthSession, task: TaskItem, completed: Boolean) {
        val client = authedClient(session)
        CalDavClient.toggleTaskComplete(client, task.toDavTask(), completed)
    }

    fun updateTask(
        session: AuthSession,
        task: TaskItem,
        summary: String,
        dueRaw: String,
        priority: Int,
        description: String,
    ) {
        val client = authedClient(session)
        CalDavClient.updateTask(
            client = client,
            task = task.toDavTask(),
            summary = summary,
            dueRaw = dueRaw,
            priority = priority,
            description = description,
        )
    }

    fun deleteTask(session: AuthSession, task: TaskItem) {
        val client = authedClient(session)
        CalDavClient.deleteTask(client, task.toDavTask())
    }

    fun deleteCompletedTasks(session: AuthSession, tasks: List<TaskItem>) {
        val client = authedClient(session)
        tasks.filter { it.isCompleted }.forEach { task ->
            CalDavClient.deleteTask(client, task.toDavTask())
        }
    }

    fun findListBySlug(lists: List<TaskListItem>, slug: String?): TaskListItem? {
        if (slug.isNullOrBlank()) return null
        return lists.firstOrNull { it.slug.equals(slug, ignoreCase = true) }
            ?: lists.firstOrNull { it.href.contains(slug, ignoreCase = true) }
    }

    private fun calendarForHref(session: AuthSession, listHref: String): DavCalendar {
        val lists = listTaskLists(session)
        val match = lists.firstOrNull { it.href == listHref }
        return DavCalendar(
            href = listHref,
            displayName = match?.displayName ?: calendarSlug(listHref),
        )
    }

    private fun DavTask.toTaskItem() = TaskItem(
        uid = uid,
        href = href,
        etag = etag,
        summary = summary,
        status = status,
        due = due,
        dueRaw = dueRaw,
        priority = priority,
        percentComplete = percentComplete,
        description = description,
        listHref = calendarHref,
        listName = calendarName,
    )

    private fun calendarSlug(href: String): String {
        val path = href.substringAfter("://").substringAfter('/').trimEnd('/')
        val decoded = runCatching {
            URLDecoder.decode(path, Charsets.UTF_8.name())
        }.getOrDefault(path)
        return decoded.substringAfterLast('/')
    }

    private fun davUserId(session: AuthSession): String =
        session.davUserId ?: OcsUserResolver.resolveDavUserId(session)

    private fun authedClient(session: AuthSession) = NetworkFactory.newAuthedClient(
        session.username,
        session.appPassword,
        session.trustAllCerts,
    )

    companion object {
        private val taskComparator = compareBy<TaskItem>(
            { it.isCompleted },
            { it.dueRaw.isBlank() },
            { it.dueRaw },
            { it.summary.lowercase() },
        )

        fun parseDueInput(input: String): String {
            val trimmed = input.trim()
            if (trimmed.isBlank()) return ""
            runCatching {
                LocalDate.parse(trimmed, DateTimeFormatter.ofPattern("dd.MM.yyyy"))
            }.onSuccess {
                return it.format(DateTimeFormatter.BASIC_ISO_DATE)
            }
            return trimmed
        }

        fun formatDueForInput(dueRaw: String): String {
            if (dueRaw.isBlank()) return ""
            return runCatching {
                when {
                    dueRaw.length >= 8 && dueRaw[8] == 'T' -> {
                        val date = dueRaw.take(8)
                        LocalDate.parse(date, DateTimeFormatter.BASIC_ISO_DATE)
                            .format(DateTimeFormatter.ofPattern("dd.MM.yyyy"))
                    }
                    dueRaw.length >= 8 -> {
                        LocalDate.parse(dueRaw.take(8), DateTimeFormatter.BASIC_ISO_DATE)
                            .format(DateTimeFormatter.ofPattern("dd.MM.yyyy"))
                    }
                    else -> dueRaw
                }
            }.getOrDefault(dueRaw)
        }
    }
}
