import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
import TextFormat
import SaveToCameraRoll
import ImageCompression
import LocalMediaResources

public extension MediaEditorScreenImpl {
    static func makeEditStoryController(
        context: AccountContext,
        peer: EnginePeer,
        storyItem: EngineStoryItem,
        videoPlaybackPosition: Double?,
        cover: Bool,
        repost: Bool,
        transitionIn: MediaEditorScreenImpl.TransitionIn,
        transitionOut: MediaEditorScreenImpl.TransitionOut?,
        completed: @escaping () -> Void = {},
        willDismiss: @escaping () -> Void = {},
        update: @escaping (Disposable?) -> Void
    ) -> MediaEditorScreenImpl? {
        guard let peerReference = PeerReference(peer._asPeer()) else {
            return nil
        }
        let subject: Signal<MediaEditorScreenImpl.Subject?, NoError>
        subject = getStorySource(engine: context.engine, peerId: peer.id, id: Int64(storyItem.id))
        |> mapToSignal { source in
            if !repost, let source {
                return .single(.draft(source, Int64(storyItem.id)))
            } else {
                let media = storyItem.media._asMedia()
                return fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: storyItem.id, media: media))
                |> mapToSignal { (value, isImage) -> Signal<MediaEditorScreenImpl.Subject?, NoError> in
                    guard case let .data(data) = value, data.complete else {
                        return .complete()
                    }
                    if let image = UIImage(contentsOfFile: data.path) {
                        return .single(nil)
                        |> then(
                            .single(.image(image: image, dimensions: PixelDimensions(image.size), additionalImage: nil, additionalImagePosition: .bottomRight))
                            |> delay(0.1, queue: Queue.mainQueue())
                        )
                    } else {
                        var duration: Double?
                        if let file = media as? TelegramMediaFile {
                            duration = file.duration
                        }
                        let symlinkPath = data.path + ".mp4"
                        if fileSize(symlinkPath) == nil {
                            let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
                        }
                        return .single(nil)
                        |> then(
                            .single(.video(videoPath: symlinkPath, thumbnail: nil, mirror: false, additionalVideoPath: nil, additionalThumbnail: nil, dimensions: PixelDimensions(width: 720, height: 1280), duration: duration ?? 0.0, videoPositionChanges: [], additionalVideoPosition: .bottomRight))
                        )
                    }
                }
            }
        }
        
        let initialCaption: NSAttributedString?
        let initialPrivacy: EngineStoryPrivacy?
        let initialMediaAreas: [MediaArea]
        if repost {
            initialCaption = nil
            initialPrivacy = nil
            initialMediaAreas = []
        } else {
            initialCaption = chatInputStateStringWithAppliedEntities(storyItem.text, entities: storyItem.entities)
            initialPrivacy = storyItem.privacy
            initialMediaAreas = storyItem.mediaAreas
        }
        
        let externalState = MediaEditorTransitionOutExternalState(
            storyTarget: nil,
            isForcedTarget: false,
            isPeerArchived: false,
            transitionOut: nil
        )
        
        var videoPlaybackPosition = videoPlaybackPosition
        if cover, case let .file(file) = storyItem.media {
            videoPlaybackPosition = 0.0
            for attribute in file.attributes {
                if case let .Video(_, _, _, _, coverTime, _) = attribute {
                    videoPlaybackPosition = coverTime
                    break
                }
            }
        }
        
        var updateProgressImpl: ((Float) -> Void)?
        let controller = MediaEditorScreenImpl(
            context: context,
            mode: .storyEditor,
            subject: subject,
            isEditing: !repost,
            isEditingCover: cover,
            forwardSource: repost ? (peer, storyItem) : nil,
            initialCaption: initialCaption,
            initialPrivacy: initialPrivacy,
            initialMediaAreas: initialMediaAreas,
            initialVideoPosition: videoPlaybackPosition,
            transitionIn: transitionIn,
            transitionOut: { finished, isNew in
                if repost && finished {
                    if let transitionOut = externalState.transitionOut?(externalState.storyTarget, externalState.isPeerArchived), let destinationView = transitionOut.destinationView {
                        return MediaEditorScreenImpl.TransitionOut(
                            destinationView: destinationView,
                            destinationRect: transitionOut.destinationRect,
                            destinationCornerRadius: transitionOut.destinationCornerRadius
                        )
                    } else {
                        return nil
                    }
                } else {
                    return transitionOut
                }
            },
            completion: { result, commit in
                let entities = generateChatInputTextEntities(result.caption)
                
                if repost {
                    let target: Stories.PendingTarget
                    let targetPeerId: EnginePeer.Id
                    if let sendAsPeerId = result.options.sendAsPeerId {
                        target = .peer(sendAsPeerId)
                        targetPeerId = sendAsPeerId
                    } else {
                        target = .myStories
                        targetPeerId = context.account.peerId
                    }
                    externalState.storyTarget = target
                    
                    completed()
                    
                    let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: targetPeerId))
                    |> deliverOnMainQueue).startStandalone(next: { peer in
                        guard let peer else {
                            return
                        }
                        
                        if case let .user(user) = peer {
                            externalState.isPeerArchived = user.storiesHidden ?? false
                            
                        } else if case let .channel(channel) = peer {
                            externalState.isPeerArchived = channel.storiesHidden ?? false
                        }
                        
                        let forwardInfo = Stories.PendingForwardInfo(peerId: peerReference.id, storyId: storyItem.id, isModified: result.media != nil)
                        
                        if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
                            var existingMedia: EngineMedia?
                            if let _ = result.media {
                            } else {
                                existingMedia = storyItem.media
                            }
                            rootController.proceedWithStoryUpload(target: target, result: result as! MediaEditorScreenResult, existingMedia: existingMedia, forwardInfo: forwardInfo, externalState: externalState, commit: commit)
                        }
                    })
                } else {
                    var updatedText: String?
                    var updatedCoverTimestamp: Double?
                    var updatedEntities: [MessageTextEntity]?
                    if result.caption.string != storyItem.text || entities != storyItem.entities {
                        updatedText = result.caption.string
                        updatedEntities = entities
                    }
                    if let coverTimestamp = result.coverTimestamp {
                        updatedCoverTimestamp = coverTimestamp
                    }
                    
                    if let mediaResult = result.media {
                        switch mediaResult {
                        case let .image(image, dimensions):
                            updateProgressImpl?(0.0)
                            
                            let tempFile = TempBox.shared.tempFile(fileName: "file")
                            defer {
                                TempBox.shared.dispose(tempFile)
                            }
                            if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) {
                                update((context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: .image(dimensions: dimensions, data: imageData, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil)
                                |> deliverOnMainQueue).startStrict(next: { result in
                                    switch result {
                                    case let .progress(progress):
                                        updateProgressImpl?(progress)
                                    case .completed:
                                        Queue.mainQueue().after(0.1) {
                                            willDismiss()
                                            
                                            HapticFeedback().success()
                                            
                                            commit({})
                                        }
                                    }
                                }))
                            }
                        case let .video(content, firstFrameImage, values, duration, dimensions):
                            updateProgressImpl?(0.0)
                            
                            if let valuesData = try? JSONEncoder().encode(values) {
                                let data = MemoryBuffer(data: valuesData)
                                let digest = MemoryBuffer(data: data.md5Digest())
                                let adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true)
                                
                                let resource: TelegramMediaResource
                                switch content {
                                case let .imageFile(path):
                                    resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
                                case let .videoFile(path):
                                    resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
                                case let .asset(localIdentifier):
                                    resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
                                }
                                
                                let tempFile = TempBox.shared.tempFile(fileName: "file")
                                defer {
                                    TempBox.shared.dispose(tempFile)
                                }
                                let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6, tempFilePath: tempFile.path) }
                                let firstFrameFile = firstFrameImageData.flatMap { data -> TempBoxFile? in
                                    let file = TempBox.shared.tempFile(fileName: "image.jpg")
                                    if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) {
                                        return file
                                    } else {
                                        return nil
                                    }
                                }
                                
                                update((context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: nil), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil)
                                |> deliverOnMainQueue).startStrict(next: { result in
                                    switch result {
                                    case let .progress(progress):
                                        updateProgressImpl?(progress)
                                    case .completed:
                                        Queue.mainQueue().after(0.1) {
                                            willDismiss()
                                            
                                            HapticFeedback().success()
                                            
                                            commit({})
                                        }
                                    }
                                }))
                            }
                        default:
                            break
                        }
                    } else if updatedText != nil || updatedCoverTimestamp != nil {
                        var media: EngineStoryInputMedia?
                        if let updatedCoverTimestamp {
                            if case let .file(file) = storyItem.media {
                                var updatedAttributes: [TelegramMediaFileAttribute] = []
                                for attribute in file.attributes {
                                    if case let .Video(duration, size, flags, preloadSize, _, videoCodec) = attribute {
                                        updatedAttributes.append(.Video(duration: duration, size: size, flags: flags, preloadSize: preloadSize, coverTime: min(duration, updatedCoverTimestamp), videoCodec: videoCodec))
                                    } else {
                                        updatedAttributes.append(attribute)
                                    }
                                }
                                media = .existing(media: file.withUpdatedAttributes(updatedAttributes))
                            }
                        }
                        let _ = (context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: media, mediaAreas: nil, text: updatedText, entities: updatedEntities, privacy: nil)
                        |> deliverOnMainQueue).startStandalone(next: { result in
                            switch result {
                            case .completed:
                                Queue.mainQueue().after(0.1) {
                                    willDismiss()
                                        
                                    HapticFeedback().success()
                                    commit({})
                                }
                            default:
                                break
                            }
                        })
                    } else {
                        willDismiss()
                        
                        HapticFeedback().success()
                        
                        commit({})
                    }
                }
            }
        )
        controller.willDismiss = willDismiss
        controller.navigationPresentation = .flatModal
        
        updateProgressImpl = { [weak controller] progress in
            controller?.updateEditProgress(progress, cancel: {
                update(nil)
            })
        }
        
        return controller
    }
}