package net.buzzert.kordophonedroid.ui.messagelist

import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.app.NotificationManagerCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.SubcomposeAsyncImage
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
import net.buzzert.kordophone.backend.model.GUID
import net.buzzert.kordophonedroid.ui.Destination
import net.buzzert.kordophonedroid.ui.LocalNavController
import net.buzzert.kordophonedroid.ui.attachments.AttachmentFetchData
import net.buzzert.kordophonedroid.ui.shared.LINK_ANNOTATION_TAG
import net.buzzert.kordophonedroid.ui.shared.linkify
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
import java.text.SimpleDateFormat
import java.time.Duration
import java.util.Date

private val IncomingChatBubbleShape = RoundedCornerShape(4.dp, 20.dp, 20.dp, 20.dp)
private val OutgoingChatBubbleShape = RoundedCornerShape(20.dp, 4.dp, 20.dp, 20.dp)

data class MessageMetadata(
    val fromAddress: String,
    val fromMe: Boolean,
    val date: Date,
    val delivered: Boolean = true,
)

interface MessageMetadataProvider {
    val metadata: MessageMetadata
}

sealed class MessageListItem: MessageMetadataProvider {
    data class TextMessage(val text: String, override val metadata: MessageMetadata): MessageListItem()
    data class ImageAttachmentMessage(val guid: String, override val metadata: MessageMetadata): MessageListItem()
}

@Composable
fun MessageListScreen(
    conversationGUID: GUID,
    viewModel: MessageListViewModel = hiltViewModel(),
) {
    viewModel.conversationGUID = conversationGUID

    // Synchronize on launch
    val context = LocalContext.current
    LaunchedEffect(Unit) {
        // Clear notifications for this conversation
        with(NotificationManagerCompat.from(context)) {
            // Not sure how to cancel individual notifications, or groups yet...
            cancelAll()
        }

        viewModel.markAsRead()
        viewModel.synchronize()
    }

    val messages by viewModel.messages.collectAsStateWithLifecycle(initialValue = listOf())

    val messageItems = mutableListOf<MessageListItem>()
    for (message in messages) {
        val metadata =  MessageMetadata(
            fromMe          = message.sender == null,
            date            = message.date,
            fromAddress     = message.sender ?: "<me>",
            delivered       = !viewModel.isPendingMessage(message)
        )

        // Collect attachments
        message.attachmentGUIDs?.let { guids ->
            guids.forEach { guid ->
                val item = MessageListItem.ImageAttachmentMessage(
                    guid = guid,
                    metadata = metadata
                )

                messageItems.add(item)
            }
        }

        val displayText = message.displayText.trim()
        if (displayText.isNotEmpty()) {
            val textMessage = MessageListItem.TextMessage(text = displayText, metadata = metadata)
            messageItems.add(textMessage)
        }
    }

    var attachmentUris by remember { mutableStateOf<Set<Uri>>(mutableSetOf()) }
    val imagePicker = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { uri: Uri? ->
        uri?.let {
            attachmentUris = attachmentUris.plus(it)
        }
    }

    val navController = LocalNavController.current
    Scaffold(
        topBar = {
            KordophoneTopAppBar(title = viewModel.title, backAction = { navController.popBackStack() })
        }) { padding ->
        MessageTranscript(
            messages = messageItems,
            paddingValues = padding,
            showSenders = viewModel.isGroupChat,
            attachmentUris = attachmentUris,
            onAddAttachment = {
                imagePicker.launch("image/*")
            },
            onClearAttachments = {
                attachmentUris = setOf()
            },
            onSendMessage = { text ->
                viewModel.enqueueOutgoingMessage(
                    text = text,
                    attachmentUris = attachmentUris,
                    context = context
                )

                // Clear pending attachments
                attachmentUris = setOf()
            }
        )
    }
}

@Composable
fun MessageTranscript(
    messages: List<MessageListItem>,
    paddingValues: PaddingValues,
    showSenders: Boolean,
    attachmentUris: Set<Uri>,
    onAddAttachment: () -> Unit,
    onClearAttachments: () -> Unit,
    onSendMessage: (text: String) -> Unit,
) {
    val scrollState = rememberLazyListState()
    var textState by rememberSaveable(stateSaver = TextFieldValue.Saver) {
        mutableStateOf(TextFieldValue())
    }

    val attachmentRowItems = attachmentUris.map {
        AttachmentRowItem(
            painter = rememberAsyncImagePainter(model = it),
            id = "attachmentID"
        )
    }

    Column(
        Modifier
            .fillMaxSize()
            .padding(paddingValues)) {

        Messages(
            messages = messages,
            modifier = Modifier.weight(1f),
            showSenders = showSenders,
            scrollState = scrollState
        )

        MessageEntry(
            onTextChanged = { textState = it },
            textFieldValue = textState,
            attachmentItems = attachmentRowItems,
            onAddAttachment = onAddAttachment,
            onClearAttachments = onClearAttachments,
            onSend = {
                onSendMessage(textState.text)

                // Clear text state
                textState = TextFieldValue()
            },
        )
    }
}

@Composable
fun Messages(
    messages: List<MessageListItem>,
    showSenders: Boolean,
    modifier: Modifier = Modifier,
    scrollState: LazyListState
) {
    Box(modifier = modifier) {
        LazyColumn(
            reverseLayout = true,
            state = scrollState,
            contentPadding = PaddingValues(vertical = 8.dp),
            modifier = Modifier
                .fillMaxSize()
                .padding(horizontal = 16.dp)
        ) {
            val dateFormatter = SimpleDateFormat.getDateTimeInstance()

            for (index in messages.indices) {
                val content = messages[index]

                var previousMessage: MessageListItem? = null
                if ((index + 1) < messages.count()) {
                    previousMessage = messages[index + 1]
                }

                val duration: Duration? = if (previousMessage != null) Duration.between(
                    previousMessage.metadata.date.toInstant(),
                    content.metadata.date.toInstant()
                ) else null

                val leapMessage = (
                    duration == null || (
                        duration.toMinutes() > 30
                    )
                )

                val repeatMessage = !leapMessage && (
                    previousMessage == null || (
                        (previousMessage.metadata.fromAddress == content.metadata.fromAddress)
                    )
                )

                // Remember: This is upside down.
                item {
                    when (content) {
                        is MessageListItem.TextMessage -> {
                            MessageBubble(
                                text = content.text,
                                mine = content.metadata.fromMe,
                                modifier = Modifier
                                    .alpha(if (!content.metadata.delivered) 0.5F else 1.0f)
                            )
                        }

                        is MessageListItem.ImageAttachmentMessage -> {
                            ImageBubble(guid = content.guid, mine = content.metadata.fromMe)
                        }
                    }


                    // Sender
                    if (!content.metadata.fromMe && showSenders && !repeatMessage) {
                        Text(
                            text = content.metadata.fromAddress,
                            style = MaterialTheme.typography.subtitle2.copy(
                                color = MaterialTheme.colors.onSurface.copy(alpha = 0.7f)
                            ),
                            modifier = Modifier.padding(vertical = 8.dp)
                        )
                    }

                    // Greater than 30 minutes: show date:
                    if (duration != null) {
                        if (duration.toMinutes() > 30) {
                            val formattedDate = dateFormatter.format(content.metadata.date)
                            Text(
                                text = formattedDate,
                                textAlign = TextAlign.Center,
                                style = MaterialTheme.typography.caption.copy(
                                    color = MaterialTheme.colors.onSurface.copy(alpha = 0.4f)
                                ),
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .padding(18.dp),
                            )
                        }

                        // Greater than five minutes: add a bit of space.
                        else if (duration.toMinutes() > 5) {
                            Spacer(modifier = Modifier.height(12.dp))
                        }
                    }
                }
            }
        }
    }
}

@Composable
fun BubbleScaffold(
    mine: Boolean,
    modifier: Modifier,
    content: @Composable () -> Unit
) {
    Column() {
        Row(modifier = modifier.fillMaxWidth()) {
            if (mine) {
                Spacer(modifier = Modifier.weight(1f))
            }

            Row(
                modifier = Modifier.fillMaxWidth(0.8f),
                horizontalArrangement = if (mine) Arrangement.End else Arrangement.Start,
            ) {
                content()
            }

            if (!mine) { Spacer(modifier = Modifier.weight(1f)) }
        }

        Spacer(modifier = Modifier.height(4.dp))
    }
}

@Composable
fun MessageBubble(
    text: String,
    mine: Boolean,
    modifier: Modifier = Modifier,
) {
    val backgroundBubbleColor = if (mine) MaterialTheme.colors.primary else MaterialTheme.colors.secondary

    // Linkify text
    val annotatedString = text.linkify()
    val urlHandler = LocalUriHandler.current

    BubbleScaffold(mine = mine, modifier = modifier) {
        Surface(
            color = backgroundBubbleColor,
            shape = if (mine) OutgoingChatBubbleShape else IncomingChatBubbleShape,
        ) {
            ClickableText(
                text = annotatedString,
                style = MaterialTheme.typography.body2,
                modifier = Modifier
                    .padding(12.dp),
                onClick = { index ->
                    annotatedString
                        .getStringAnnotations(LINK_ANNOTATION_TAG, index, index)
                        .firstOrNull()?.let {
                            urlHandler.openUri(it.item)
                        }
                }
            )
        }
    }
}

@Composable
fun ImageBubble(
    guid: String,
    mine: Boolean,
    modifier: Modifier = Modifier,
) {
    val shape: RoundedCornerShape = if (mine) OutgoingChatBubbleShape else IncomingChatBubbleShape
    val attachmentFetchData = AttachmentFetchData(guid, preview = true)
    val navController = LocalNavController.current

    BubbleScaffold(mine = mine, modifier = modifier) {
        SubcomposeAsyncImage(
            model = ImageRequest.Builder(LocalContext.current)
                .data(attachmentFetchData)
                .crossfade(true)
                .build(),
            loading = {
                Box(
                    modifier = Modifier
                        .background(Color.LightGray)
                        .size(width = 220.dp, height = 200.dp)
                ) {
                    CircularProgressIndicator(
                        modifier = Modifier.align(Alignment.Center)
                    )
                }
            },
            error = {
                val error = it.result.throwable.message
                Surface(
                    color = Color.Red
                ) {
                    Column(modifier = Modifier.padding(20.dp)) {
                        Text(
                            text = "Error loading attachment",
                            style = TextStyle.Default.copy(
                                fontWeight = FontWeight.Bold,
                                textAlign = TextAlign.Center,
                            )
                        )

                        Text("$error")
                    }
                }
            },
            contentDescription = "Image attachment",
            modifier = Modifier
                .clip(shape)
                .clickable {
                    navController.navigate(Destination.AttachmentViewer.createRoute(guid))
                }
        )
    }
}
