//
//  AttachmentViewModel+Load.swift
//  
//
//  Created by MainasuK on 2022/11/8.
//

import UIKit
import AVKit
import UniformTypeIdentifiers
import Nuke

extension AttachmentViewModel {
    
    @MainActor
    func load(input: Input) async throws -> Output {
        switch input {
        case .image(let image):
            guard let data = image.normalized()?.pngData() else {
                throw AttachmentError.invalidAttachmentType
            }
            return .image(data, imageKind: .png)
        case .url(let url):
            do {
                let output = try await AttachmentViewModel.load(url: url)
                return output
            } catch {
                throw error
            }
        case .mastodonAssetUrl(let url, _):
            return try await Self.loadMastodonAsset(url: url)
        case .pickerResult(let pickerResult):
            do {
                let output = try await AttachmentViewModel.load(itemProvider: pickerResult.itemProvider)
                return output
            } catch {
                throw error
            }
        case .itemProvider(let itemProvider):
            do {
                let output = try await AttachmentViewModel.load(itemProvider: itemProvider)
                return output
            } catch {
                throw error
            }
        }
    }
    
    private static func loadMastodonAsset(url: URL) async throws -> Output {
        guard !url.isFileURL else {
            throw AttachmentError.invalidAttachmentType
        }
        let (imageData, _) = try await URLSession.shared.data(from: url)
        return .image(imageData, imageKind: AssetType(imageData) == .png ? .png : .jpg)
    }
    
    private static func load(url: URL) async throws -> Output {
        guard let uti = UTType(filenameExtension: url.pathExtension) else {
            throw AttachmentError.invalidAttachmentType
        }
        
        if uti.conforms(to: .image) {
            guard url.startAccessingSecurityScopedResource() else {
                throw AttachmentError.invalidAttachmentType
            }
            defer { url.stopAccessingSecurityScopedResource() }
            let imageData = try Data(contentsOf: url)
            return .image(imageData, imageKind: AssetType(imageData) == .png ? .png : .jpg)
        } else if uti.conforms(to: .movie) {
            guard url.startAccessingSecurityScopedResource() else {
                throw AttachmentError.invalidAttachmentType
            }
            defer { url.stopAccessingSecurityScopedResource() }
            
            let fileName = UUID().uuidString
            let tempDirectoryURL = FileManager.default.temporaryDirectory
            let fileURL = tempDirectoryURL.appendingPathComponent(fileName).appendingPathExtension(url.pathExtension)
            try FileManager.default.createDirectory(at: tempDirectoryURL, withIntermediateDirectories: true, attributes: nil)
            try FileManager.default.copyItem(at: url, to: fileURL)
            return .video(fileURL, mimeType: UTType.movie.preferredMIMEType ?? "video/mp4")
        } else {
            throw AttachmentError.invalidAttachmentType
        }
    }
    
    private static func load(itemProvider: NSItemProvider) async throws -> Output {
        if itemProvider.isImage() {
            guard let result = try await itemProvider.loadImageData() else {
                throw AttachmentError.invalidAttachmentType
            }
            let imageKind: Output.ImageKind = {
                if let type = result.type {
                    if type == UTType.png {
                        return .png
                    }
                    if type == UTType.jpeg {
                        return .jpg
                    }
                }
                
                let imageData = result.data
                let assetType = AssetType(imageData)

                if assetType == .png {
                    return .png
                }
                if assetType == .jpeg {
                    return .jpg
                }
                
                assertionFailure("unknown image kind")
                return .jpg
            }()
            return .image(result.data, imageKind: imageKind)
        } else if itemProvider.isMovie() {
            guard let result = try await itemProvider.loadVideoData() else {
                throw AttachmentError.invalidAttachmentType
            }
            return .video(result.url, mimeType: "video/mp4")
        } else {
            assertionFailure()
            throw AttachmentError.invalidAttachmentType
        }
    }

}

extension AttachmentViewModel {
    static func createThumbnailForVideo(url: URL) -> UIImage? {
        guard FileManager.default.fileExists(atPath: url.path) else { return nil }
        let asset = AVURLAsset(url: url)
        let assetImageGenerator = AVAssetImageGenerator(asset: asset)
        assetImageGenerator.appliesPreferredTrackTransform = true   // fix orientation
        do {
            let cgImage = try assetImageGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil)
            let image = UIImage(cgImage: cgImage)
            return image
        } catch {
            return nil
        }
    }
}

extension NSItemProvider {
    func isImage() -> Bool {
        return hasRepresentationConforming(
            toTypeIdentifier: UTType.image.identifier,
            fileOptions: []
        )
    }
    
    func isMovie() -> Bool {
        return hasRepresentationConforming(
            toTypeIdentifier: UTType.movie.identifier,
            fileOptions: []
        )
    }
}
