import Postbox
import TelegramCore
import TelegramUI

func makePeerIdFromBridgeIdentifier(_ identifier: Int64) -> PeerId? {
    if identifier < 0 && identifier > Int32.min {
        return PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(clamping: -identifier))
    } else if identifier < Int64(Int32.min) * 2 && identifier > Int64(Int32.min) * 3 {
        return PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(clamping: Int64(Int32.min) &* 2 &- identifier))
    } else if identifier > 0 && identifier < Int32.max {
        return PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(clamping: identifier))
    } else {
        return nil
    }
}

func makeBridgeIdentifier(_ peerId: PeerId) -> Int64 {
    switch peerId.namespace {
        case Namespaces.Peer.CloudGroup:
            return -Int64(peerId.id)
        case Namespaces.Peer.CloudChannel:
            return Int64(Int32.min) * 2 - Int64(peerId.id)
        default:
            return Int64(peerId.id)
    }
}

func makeBridgeDeliveryState(_ message: Message?) -> TGBridgeMessageDeliveryState {
    if let message = message {
        if message.flags.contains(.Failed) {
            return .failed
        }
        else if message.flags.contains(.Sending) {
            return .pending
        }
    }
    return .delivered
}

private func makeBridgeImage(_ image: TelegramMediaImage?) -> TGBridgeImageMediaAttachment? {
    if let image = image, let representation = largestImageRepresentation(image.representations) {
        let bridgeImage = TGBridgeImageMediaAttachment()
        bridgeImage.imageId = image.imageId.id
        bridgeImage.dimensions = representation.dimensions
        return bridgeImage
    } else {
        return nil
    }
}

func makeBridgeDocument(_ file: TelegramMediaFile?) -> TGBridgeDocumentMediaAttachment? {
    if let file = file {
        let bridgeDocument = TGBridgeDocumentMediaAttachment()
        bridgeDocument.documentId = file.fileId.id
        bridgeDocument.fileSize = Int32(file.size ?? 0)
        for attribute in file.attributes {
            switch attribute {
                case let .FileName(fileName):
                    bridgeDocument.fileName = fileName
                case .Animated:
                    bridgeDocument.isAnimated = true
                case let .ImageSize(size):
                    bridgeDocument.imageSize = NSValue(cgSize: size)
                case let .Sticker(displayText, packReference, _):
                    bridgeDocument.isSticker = true
                    bridgeDocument.stickerAlt = displayText
                    if let packReference = packReference, case let .id(id, accessHash) = packReference {
                        bridgeDocument.stickerPackId = id
                        bridgeDocument.stickerPackAccessHash = accessHash
                    }
                case let .Audio(_, duration, title, performer, _):
                    bridgeDocument.duration = Int32(clamping: duration)
                    bridgeDocument.title = title
                    bridgeDocument.performer = performer
                default:
                    break
            }
        }
        return bridgeDocument
    }
    return nil
}

func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: Peer? = nil, filterUnsupportedActions: Bool = true) -> [TGBridgeMediaAttachment] {
    var bridgeMedia: [TGBridgeMediaAttachment] = []
    
    if let forward = message.forwardInfo {
        let bridgeForward = TGBridgeForwardedMessageMediaAttachment()
        bridgeForward.peerId = forward.author.flatMap({ makeBridgeIdentifier($0.id) }) ?? 0
        if let sourceMessageId = forward.sourceMessageId {
            bridgeForward.mid = sourceMessageId.id
        }
        bridgeForward.date = forward.date
        bridgeMedia.append(bridgeForward)
    }
    
    for attribute in message.attributes {
        if let reply = attribute as? ReplyMessageAttribute, let replyMessage = message.associatedMessages[reply.messageId] {
            let bridgeReply = TGBridgeReplyMessageMediaAttachment()
            bridgeReply.mid = reply.messageId.id
            bridgeReply.message = makeBridgeMessage(replyMessage, strings: strings)
            bridgeMedia.append(bridgeReply)
        } else if let entities = attribute as? TextEntitiesMessageAttribute {
            var bridgeEntities: [Any] = []
            for entity in entities.entities {
                var bridgeEntity: TGBridgeMessageEntity? = nil
                switch entity.type {
                    case .Url:
                        bridgeEntity = TGBridgeMessageEntityUrl()
                        bridgeEntity?.range = NSRange(entity.range)
                    case .TextUrl:
                        bridgeEntity = TGBridgeMessageEntityTextUrl()
                        bridgeEntity?.range = NSRange(entity.range)
                    case .Email:
                        bridgeEntity = TGBridgeMessageEntityEmail()
                        bridgeEntity?.range = NSRange(entity.range)
                    case .Mention:
                        bridgeEntity = TGBridgeMessageEntityMention()
                        bridgeEntity?.range = NSRange(entity.range)
                    case .Hashtag:
                        bridgeEntity = TGBridgeMessageEntityHashtag()
                        bridgeEntity?.range = NSRange(entity.range)
                    case .BotCommand:
                        bridgeEntity = TGBridgeMessageEntityBotCommand()
                        bridgeEntity?.range = NSRange(entity.range)
                    case .Bold:
                        bridgeEntity = TGBridgeMessageEntityBold()
                        bridgeEntity?.range = NSRange(entity.range)
                    case .Italic:
                        bridgeEntity = TGBridgeMessageEntityItalic()
                        bridgeEntity?.range = NSRange(entity.range)
                    case .Code:
                        bridgeEntity = TGBridgeMessageEntityCode()
                        bridgeEntity?.range = NSRange(entity.range)
                    case .Pre:
                        bridgeEntity = TGBridgeMessageEntityPre()
                        bridgeEntity?.range = NSRange(entity.range)
                    default:
                        break
                }
                if let bridgeEntity = bridgeEntity {
                    bridgeEntities.append(bridgeEntity)
                }
            }
            if !bridgeEntities.isEmpty {
                let attachment = TGBridgeMessageEntitiesAttachment()
                attachment.entities = bridgeEntities
                bridgeMedia.append(attachment)
            }
        }
    }
    
    for m in message.media {
        if let image = m as? TelegramMediaImage, let bridgeImage = makeBridgeImage(image) {
            bridgeMedia.append(bridgeImage)
        }
        else if let file = m as? TelegramMediaFile {
            if file.isVideo {
                let bridgeVideo = TGBridgeVideoMediaAttachment()
                bridgeVideo.videoId = file.fileId.id
                
                for attribute in file.attributes {
                    switch attribute {
                        case let .Video(duration, size, flags):
                            bridgeVideo.duration = Int32(clamping: duration)
                            bridgeVideo.dimensions = size
                            bridgeVideo.round = flags.contains(.instantRoundVideo)
                        default:
                            break
                    }
                }
                
                bridgeMedia.append(bridgeVideo)
            } else if file.isVoice {
                let bridgeAudio = TGBridgeAudioMediaAttachment()
                bridgeAudio.audioId = file.fileId.id
                bridgeAudio.fileSize = Int32(clamping: file.size ?? 0)
                
                for attribute in file.attributes {
                    switch attribute {
                        case let .Audio(_, duration, _, _, _):
                            bridgeAudio.duration = Int32(clamping: duration)
                        default:
                            break
                    }
                }
                
                bridgeMedia.append(bridgeAudio)
            } else if let bridgeDocument = makeBridgeDocument(file) {
                bridgeMedia.append(bridgeDocument)
            }
        } else if let action = m as? TelegramMediaAction {
            var bridgeAction: TGBridgeActionMediaAttachment? = nil
            var consumed = false
            switch action.action {
                case let .groupCreated(title):
                    bridgeAction = TGBridgeActionMediaAttachment()
                    if chatPeer is TelegramGroup {
                        bridgeAction?.actionType = .createChat
                        bridgeAction?.actionData = ["title": title]
                    } else if let channel = chatPeer as? TelegramChannel {
                        if case .group = channel.info {
                            bridgeAction?.actionType = .createChat
                            bridgeAction?.actionData = ["title": title]
                        } else {
                            bridgeAction?.actionType = .channelCreated
                        }
                    }
                case let .phoneCall(_, discardReason, _):
                    let bridgeAttachment = TGBridgeUnsupportedMediaAttachment()
                    let incoming = message.flags.contains(.Incoming)
                    var compactTitle: String = ""
                    var subTitle: String = ""
                    if let discardReason = discardReason {
                        switch discardReason {
                            case .busy, .disconnect:
                                compactTitle = strings.Notification_CallCanceled
                                subTitle = strings.Notification_CallCanceledShort
                            case .missed:
                                compactTitle = incoming ? strings.Notification_CallMissed : strings.Notification_CallCanceled
                                subTitle = incoming ? strings.Notification_CallMissedShort : strings.Notification_CallCanceledShort
                            case .hangup:
                                break
                        }
                    }
                    if compactTitle.isEmpty {
                        compactTitle = incoming ? strings.Notification_CallIncoming : strings.Notification_CallOutgoing
                        subTitle = incoming ? strings.Notification_CallIncomingShort : strings.Notification_CallOutgoingShort
                    }
                    bridgeAttachment.compactTitle = compactTitle
                    bridgeAttachment.title = strings.Watch_Message_Call
                    bridgeAttachment.subtitle = subTitle
                    bridgeMedia.append(bridgeAttachment)
                    consumed = true
                default:
                    break
            }
            if let bridgeAction = bridgeAction {
                bridgeMedia.append(bridgeAction)
            } else if !consumed && !filterUnsupportedActions {
                let bridgeAttachment = TGBridgeUnsupportedMediaAttachment()
                bridgeAttachment.compactTitle = ""
                bridgeAttachment.title = ""
                bridgeMedia.append(bridgeAttachment)
            }
        } else if let poll = m as? TelegramMediaPoll {
            let bridgeAttachment = TGBridgeUnsupportedMediaAttachment()
            bridgeAttachment.compactTitle = strings.Watch_Message_Poll
            bridgeAttachment.title = strings.Watch_Message_Poll
            bridgeAttachment.subtitle = poll.text
            bridgeMedia.append(bridgeAttachment)
        } else if let contact = m as? TelegramMediaContact {
            let bridgeContact = TGBridgeContactMediaAttachment()
            if let peerId = contact.peerId {
                bridgeContact.uid = Int32(clamping: makeBridgeIdentifier(peerId))
            }
            bridgeContact.firstName = contact.firstName
            bridgeContact.lastName = contact.lastName
            bridgeContact.phoneNumber = contact.phoneNumber
            bridgeContact.prettyPhoneNumber = formatPhoneNumber(contact.phoneNumber)
            bridgeMedia.append(bridgeContact)
        } else if let map = m as? TelegramMediaMap {
            let bridgeLocation = TGBridgeLocationMediaAttachment()
            bridgeLocation.latitude = map.latitude
            bridgeLocation.longitude = map.longitude
            if let venue = map.venue {
                let bridgeVenue = TGBridgeVenueAttachment()
                bridgeVenue.title = venue.title
                bridgeVenue.address = venue.address
                bridgeVenue.provider = venue.provider
                bridgeVenue.venueId = venue.id
                bridgeLocation.venue = bridgeVenue
            }
            bridgeMedia.append(bridgeLocation)
        } else if let webpage = m as? TelegramMediaWebpage {
            if case let .Loaded(content) = webpage.content {
                let bridgeWebpage = TGBridgeWebPageMediaAttachment()
                bridgeWebpage.webPageId = webpage.id?.id ?? 0
                bridgeWebpage.url = content.url
                bridgeWebpage.displayUrl = content.displayUrl
                bridgeWebpage.pageType = content.type
                bridgeWebpage.siteName = content.websiteName
                bridgeWebpage.title = content.title
                bridgeWebpage.pageDescription = content.text
                bridgeWebpage.photo = makeBridgeImage(content.image)
                bridgeWebpage.embedUrl = content.embedUrl
                bridgeWebpage.embedType = content.embedType
                bridgeWebpage.embedSize = content.embedSize ?? CGSize()
                bridgeWebpage.duration = NSNumber(integerLiteral: content.duration ?? 0)
                bridgeWebpage.author = content.author
                bridgeMedia.append(bridgeWebpage)
            }
        } else if let game = m as? TelegramMediaGame {
            let bridgeAttachment = TGBridgeUnsupportedMediaAttachment()
            bridgeAttachment.compactTitle = game.title
            bridgeAttachment.title = strings.Watch_Message_Game
            bridgeAttachment.subtitle = game.title
            bridgeMedia.append(bridgeAttachment)
        } else if let invoice = m as? TelegramMediaInvoice {
            let bridgeAttachment = TGBridgeUnsupportedMediaAttachment()
            bridgeAttachment.compactTitle = invoice.title
            bridgeAttachment.title = strings.Watch_Message_Invoice
            bridgeAttachment.subtitle = invoice.title
            bridgeMedia.append(bridgeAttachment)
        } else if let _ = m as? TelegramMediaUnsupported {
            let bridgeAttachment = TGBridgeUnsupportedMediaAttachment()
            bridgeAttachment.compactTitle = strings.Watch_Message_Unsupported
            bridgeAttachment.title = strings.Watch_Message_Unsupported
            bridgeAttachment.subtitle = ""
            bridgeMedia.append(bridgeAttachment)
        }
    }
    return bridgeMedia
}

func makeBridgeChat(_ entry: ChatListEntry, strings: PresentationStrings) -> (TGBridgeChat, [Int64 : TGBridgeUser])? {
    if case let .MessageEntry(index, message, readState, _, _, renderedPeer, _) = entry {
        guard index.messageIndex.id.peerId.namespace != Namespaces.Peer.SecretChat else {
            return nil
        }
        let (bridgeChat, participants) = makeBridgeChat(renderedPeer.peer)
        bridgeChat.date = TimeInterval(index.messageIndex.timestamp)
        if let message = message {
            if let author = message.author {
                bridgeChat.fromUid = Int32(clamping: makeBridgeIdentifier(author.id))
            }
            bridgeChat.text = message.text
            bridgeChat.outgoing = !message.flags.contains(.Incoming)
            bridgeChat.deliveryState = makeBridgeDeliveryState(message)
            bridgeChat.deliveryError = message.flags.contains(.Failed)
            bridgeChat.media = makeBridgeMedia(message: message, strings: strings, filterUnsupportedActions: false)
        }
        bridgeChat.unread = readState?.isUnread ?? false
        bridgeChat.unreadCount = readState?.count ?? 0
        
        var bridgeUsers: [Int64 : TGBridgeUser] = participants
        if let bridgeUser = makeBridgeUser(message?.author, presence: nil) {
            bridgeUsers[bridgeUser.identifier] = bridgeUser
        }
        if let user = renderedPeer.peer as? TelegramUser, user.id != message?.author?.id, let bridgeUser = makeBridgeUser(user, presence: nil) {
            bridgeUsers[bridgeUser.identifier] = bridgeUser
        }
        
        return (bridgeChat, bridgeUsers)
    }
    return nil
}

func makeBridgeChat(_ peer: Peer?, view: PeerView? = nil) -> (TGBridgeChat, [Int64 : TGBridgeUser]) {
    let bridgeChat = TGBridgeChat()
    var bridgeUsers: [Int64 : TGBridgeUser] = [:]
    if let peer = peer {
        bridgeChat.identifier = makeBridgeIdentifier(peer.id)
        bridgeChat.userName = peer.addressName
    }
    if let group = peer as? TelegramGroup {
        bridgeChat.isGroup = true
        bridgeChat.groupTitle = group.title
        bridgeChat.participantsCount = Int32(clamping: group.participantCount)
        
        if let representation = smallestImageRepresentation(group.photo) {
            bridgeChat.groupPhotoSmall = legacyImageLocationUri(resource: representation.resource)
        }
        if let representation = largestImageRepresentation(group.photo) {
            bridgeChat.groupPhotoBig = legacyImageLocationUri(resource: representation.resource)
        }
        if let view = view, let cachedData = view.cachedData as? CachedGroupData, let participants = cachedData.participants {
            bridgeChat.participantsCount = Int32(clamping: participants.participants.count)
            var bridgeParticipants: [Int64] = []
            for participant in participants.participants {
                if let user = view.peers[participant.peerId], let bridgeUser = makeBridgeUser(user, presence: view.peerPresences[user.id]) {
                    bridgeParticipants.append(bridgeUser.identifier)
                    bridgeUsers[bridgeUser.identifier] = bridgeUser
                }
            }
            bridgeChat.participants = bridgeParticipants
        }
    } else if let channel = peer as? TelegramChannel {
        bridgeChat.isChannel = true
        bridgeChat.groupTitle = channel.title
        if case .group = channel.info {
            bridgeChat.isChannelGroup = true
        }
        bridgeChat.verified = channel.flags.contains(.isVerified)
        
        if let representation = smallestImageRepresentation(channel.photo) {
            bridgeChat.groupPhotoSmall = legacyImageLocationUri(resource: representation.resource)
        }
        if let representation = largestImageRepresentation(channel.photo) {
            bridgeChat.groupPhotoBig = legacyImageLocationUri(resource: representation.resource)
        }
        if let view = view, let cachedData = view.cachedData as? CachedChannelData {
            bridgeChat.about = cachedData.about
        }
    }
    
    //            _hasLeftGroup = [aDecoder decodeBoolForKey:TGBridgeChatHasLeftGroupKey];
    //            _isKickedFromGroup = [aDecoder decodeBoolForKey:TGBridgeChatIsKickedFromGroupKey];
    return (bridgeChat, bridgeUsers)
}

func makeBridgeUser(_ peer: Peer?, presence: PeerPresence? = nil, cachedData: CachedPeerData? = nil) -> TGBridgeUser? {
    if let user = peer as? TelegramUser {
        let bridgeUser = TGBridgeUser()
        bridgeUser.identifier = makeBridgeIdentifier(user.id)
        bridgeUser.firstName = user.firstName
        bridgeUser.lastName = user.lastName
        bridgeUser.userName = user.addressName
        bridgeUser.phoneNumber = user.phone
        if let phone = user.phone {
            bridgeUser.prettyPhoneNumber = formatPhoneNumber(phone)
        }
        if let presence = presence as? TelegramUserPresence {
            let timestamp = 0
            switch presence.status {
                case .recently:
                    bridgeUser.lastSeen = -2
                case .lastWeek:
                    bridgeUser.lastSeen = -3
                case .lastMonth:
                    bridgeUser.lastSeen = -4
                case .none:
                    bridgeUser.lastSeen = -5
                case let .present(statusTimestamp):
                    if statusTimestamp > timestamp {
                        bridgeUser.online = true
                    }
                    bridgeUser.lastSeen = TimeInterval(statusTimestamp)
            }
        }
        if let cachedData = cachedData as? CachedUserData {
            bridgeUser.about = cachedData.about
        }
        if let representation = smallestImageRepresentation(user.photo) {
            bridgeUser.photoSmall = legacyImageLocationUri(resource: representation.resource)
        }
        if let representation = largestImageRepresentation(user.photo) {
            bridgeUser.photoBig = legacyImageLocationUri(resource: representation.resource)
        }
        if user.botInfo != nil {
            bridgeUser.kind = .bot
            bridgeUser.botKind = .generic
        }
        bridgeUser.verified = user.flags.contains(.isVerified)
        return bridgeUser
    } else {
        return nil
    }
}

func makeBridgePeers(_ message: Message) -> [Int64 : Any] {
    var bridgeUsers: [Int64 : Any] = [:]
    for (_, peer) in message.peers {
        if peer is TelegramUser, let bridgeUser = makeBridgeUser(peer, presence: nil) {
            bridgeUsers[bridgeUser.identifier] = bridgeUser
        } else if peer is TelegramGroup || peer is TelegramChannel {
            let bridgeChat = makeBridgeChat(peer)
            bridgeUsers[bridgeChat.0.identifier] = bridgeChat.0
        }
    }
    if let author = message.author, let bridgeUser = makeBridgeUser(author) {
        bridgeUsers[bridgeUser.identifier] = bridgeUser
    }
    return bridgeUsers
}

func makeBridgeMessage(_ entry: MessageHistoryEntry, strings: PresentationStrings) -> (TGBridgeMessage, [Int64 : TGBridgeUser])? {
    guard let bridgeMessage = makeBridgeMessage(entry.message, strings: strings) else {
        return nil
    }
    if entry.message.id.namespace == Namespaces.Message.Local && !entry.message.flags.contains(.Failed) {
        return nil
    }
    
    bridgeMessage.unread = !entry.isRead
    
    var bridgeUsers: [Int64 : TGBridgeUser] = [:]
    if let bridgeUser = makeBridgeUser(entry.message.author, presence: nil) {
        bridgeUsers[bridgeUser.identifier] = bridgeUser
    }
    for (_, peer) in entry.message.peers {
        if let bridgeUser = makeBridgeUser(peer, presence: nil) {
            bridgeUsers[bridgeUser.identifier] = bridgeUser
        }
    }
    
    return (bridgeMessage, bridgeUsers)
}

func makeBridgeMessage(_ message: Message, strings: PresentationStrings, chatPeer: Peer? = nil) -> TGBridgeMessage? {
    var chatPeer = chatPeer
    if chatPeer == nil {
        chatPeer = message.peers[message.id.peerId]
    }
    
    let bridgeMessage = TGBridgeMessage()
    bridgeMessage.identifier = message.id.id
    bridgeMessage.date = TimeInterval(message.timestamp)
    bridgeMessage.randomId = message.globallyUniqueId ?? 0
//    bridgeMessage.unread = false
    bridgeMessage.outgoing = !message.flags.contains(.Incoming)
    if let author = message.author {
        bridgeMessage.fromUid = makeBridgeIdentifier(author.id)
    }
    bridgeMessage.toUid = makeBridgeIdentifier(message.id.peerId)
    bridgeMessage.cid = makeBridgeIdentifier(message.id.peerId)
    bridgeMessage.text = message.text
    bridgeMessage.deliveryState = makeBridgeDeliveryState(message)
    bridgeMessage.media = makeBridgeMedia(message: message, strings: strings, chatPeer: chatPeer)
    return bridgeMessage
}

func makeVenue(from bridgeVenue: TGBridgeVenueAttachment?) -> MapVenue? {
    if let bridgeVenue = bridgeVenue {
        return MapVenue(title: bridgeVenue.title, address: bridgeVenue.address, provider: bridgeVenue.provider, id: bridgeVenue.venueId, type: "")
    }
    return nil
}

func makeBridgeLocationVenue(_ contextResult: ChatContextResultMessage) -> TGBridgeLocationVenue? {
    if case let .mapLocation(mapMedia, _) = contextResult {
        let bridgeVenue = TGBridgeLocationVenue()
        bridgeVenue.coordinate = CLLocationCoordinate2D(latitude: mapMedia.latitude, longitude: mapMedia.longitude)
        if let venue = mapMedia.venue {
            bridgeVenue.name = venue.title
            bridgeVenue.address = venue.address
            bridgeVenue.provider = venue.provider
            bridgeVenue.identifier = venue.id
        }
        return bridgeVenue
    }
    return nil
}
