import BackendEnvironmentClient
import ComponentLibrary
import ComposableArchitecture
import Foundation
import Localization
import MessageUI
import Photos
import StatsigClient
import SwiftUI
import TikTokOpenShareSDK

private extension Bundle {
    var facebookAppID: String {
        infoDictionary?["FACEBOOK_APP_ID"] as? String ?? "478115168068797"
    }
}

public struct ShareDestination: Identifiable, Equatable {
    public struct ShareData {
        let shareURL: URL
        let stickerData: Data
        let videoURL: URL?
        let downloadableURL: URL?
        let subject: String
        let message: String?
        let isAuthorOfClip: Bool
    }

    public enum SharePresence {
        case systemLink
        case systemCopy
        case systemEmail
        case external(((URL) -> Bool) -> Bool)

        public func isAvailable(_ canOpenURL: (URL) -> Bool) -> Bool {
            guard case let .external(isAvailable) = self else { return true }
            return isAvailable(canOpenURL)
        }

        public var isSystemLink: Bool {
            guard case .systemLink = self else { return false }
            return true
        }
    }

    public let id = UUID()
    /// like `reddit`, `facebook`, `x`. used to for share link tracking attribution
    public let platformName: String
    let name: String
    let icon: Image
    let needsVideo: Bool
    public var presence: SharePresence
    public var requiresWatermark: Bool
    public var isDownloadEvent: Bool
    public var share: (ShareData) async throws -> String?

    public static func == (lhs: ShareDestination, rhs: ShareDestination) -> Bool {
        lhs.id == rhs.id
    }
}

enum SharingError: LocalizedError {
    case invalidShareId
    case invalidURL
    case watermarkingFailed
    case requiredPhotosLibraryAccessMissing
    case saveVideoToPhotosLibraryFailed

    var errorDescription: String? {
        switch self {
        case .invalidShareId: "Invalid share ID"
        case .invalidURL: "Invalid share url"
        case .requiredPhotosLibraryAccessMissing: "Required photos library access missing"
        case .saveVideoToPhotosLibraryFailed: "Failed to save video to photos library"
        case .watermarkingFailed: "Failed to save shareable video"
        }
    }
}

extension ShareDestination {
    static let copyLink = ShareDestination(
        platformName: "ios_native_copy_link",
        name: L10n.FeatureShare.copyLink,
        icon: Image.Icon.linkShare,
        needsVideo: false,
        presence: .systemCopy,
        requiresWatermark: false,
        isDownloadEvent: false,
        share: { shareData in
            UIPasteboard.general.string = shareData.shareURL.absoluteString
            return L10n.FeatureShare.copiedLink
        }
    )
    static let system = ShareDestination(
        platformName: "ios_native",
        name: L10n.FeatureShare.more,
        icon: Image.Icon.moreVertical,
        needsVideo: false,
        presence: .systemLink,
        requiresWatermark: false,
        isDownloadEvent: false,
        share: { @MainActor shareData in
            let activity = UIActivityViewController(activityItems: [shareData.shareURL], applicationActivities: nil)
            UIApplication.shared.topViewController?.present(activity, animated: true, completion: nil)
            return nil
        }
    )
    static var x = ShareDestination(
        platformName: "x",
        name: L10n.FeatureShare.twitter,
        icon: Image.Icon.xShare,
        needsVideo: false,
        presence: .external { _ in true },
        requiresWatermark: false,
        isDownloadEvent: false,
        share: { @MainActor shareData in
            guard
                let shareURLencoded = shareData.shareURL.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
                let messageEncoded = shareData.subject.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
                let url = URL(string: "https://x.com/intent/tweet?url=\(shareURLencoded)&text=\(messageEncoded)")
            else { return nil }
            await UIApplication.shared.open(url)
            return nil
        }
    )

    static let whatsApp = ShareDestination(
        platformName: "whatsapp",
        name: L10n.FeatureShare.whatsapp,
        icon: Image.Icon.whatsAppShare,
        needsVideo: false,
        presence: .external { canOpenURL in
            guard let url = URL(string: "whatsapp://") else {
                return false
            }
            return canOpenURL(url)
        },
        requiresWatermark: false,
        isDownloadEvent: false,
        share: { @MainActor shareData in
            guard let encodedLink = "\(shareData.subject)\n\n\(shareData.shareURL.absoluteString)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
                  let url = URL(string: "whatsapp://send?text=\(encodedLink)")
            else {
                throw SharingError.invalidURL
            }
            await UIApplication.shared.open(url)
            return nil
        }
    )

    static let instagram = ShareDestination(
        platformName: "instagram",
        name: L10n.FeatureShare.instagram,
        icon: Image.Icon.instaShare,
        needsVideo: true,
        presence: .external { canOpenURL in
            let facebookAppID = Bundle.main.facebookAppID
            guard let url = URL(string: "instagram-stories://share?source_application=\(facebookAppID)") else {
                return false
            }
            return canOpenURL(url)
        },
        requiresWatermark: true,
        isDownloadEvent: false,
        share: { @MainActor shareData in
            let faceBookAppID = Bundle.main.facebookAppID
            guard let url = URL(string: "instagram-stories://share?source_application=\(faceBookAppID)") else { throw SharingError.invalidURL }

            prepareInstagram(shareData: shareData)
            await UIApplication.shared.open(url)
            return nil

            func prepareInstagram(shareData: ShareData) {
                var videoData: Data {
                    shareData.downloadableURL.flatMap { try? Data(contentsOf: $0) } ?? .init()
                }
                let linkText = shareData.isAuthorOfClip ? L10n.FeatureShare.shareMySongSubject : L10n.FeatureShare.shareSongSubject
                let pasteBoardItems: [String: Any] = [
                    "com.instagram.sharedSticker.stickerImage": shareData.stickerData,
                    "com.instagram.sharedSticker.backgroundVideo": videoData,
                    "com.instagram.sharedSticker.appID": faceBookAppID,
                    "com.instagram.sharedSticker.linkURL": shareData.shareURL.absoluteString,
                    "com.instagram.sharedSticker.linkText": linkText,
                ]
                let pasteboardOptions = [
                    UIPasteboard.OptionsKey.expirationDate: Date().addingTimeInterval(300),
                ]
                UIPasteboard.general.setItems([pasteBoardItems], options: pasteboardOptions)
            }
        }
    )

    static let facebook = ShareDestination(
        platformName: "facebook",
        name: L10n.FeatureShare.facebook,
        icon: Image.Icon.facebookShare,
        needsVideo: true,
        presence: .external { canOpenURL in
            let facebookAppID = Bundle.main.facebookAppID
            guard let url = URL(string: "facebook-stories://share?source_application=\(facebookAppID)") else {
                return false
            }
            return canOpenURL(url)
        },
        requiresWatermark: true,
        isDownloadEvent: false,
        share: { @MainActor shareData in
            let faceBookAppID = Bundle.main.facebookAppID
            guard let url = URL(string: "facebook-stories://share?source_application=\(faceBookAppID)") else { throw SharingError.invalidURL }

            prepareFacebook(shareData: shareData)
            await UIApplication.shared.open(url)
            return nil

            func prepareFacebook(shareData: ShareData) {
                var videoData: Data {
                    shareData.downloadableURL.flatMap { try? Data(contentsOf: $0) } ?? .init()
                }
                let pasteBoardItems: [String: Any] = [
                    "com.facebook.sharedSticker.stickerImage": shareData.stickerData,
                    "com.facebook.sharedSticker.backgroundVideo": videoData,
                    "com.facebook.sharedSticker.appID": faceBookAppID,
                ]
                let pasteboardOptions = [
                    UIPasteboard.OptionsKey.expirationDate: Date().addingTimeInterval(300),
                ]
                UIPasteboard.general.setItems([pasteBoardItems], options: pasteboardOptions)
            }
        }
    )
    static let email = ShareDestination(
        platformName: "email",
        name: L10n.FeatureShare.email,
        icon: Image(systemName: "envelope.fill"),
        needsVideo: false,
        presence: .systemEmail,
        requiresWatermark: false,
        isDownloadEvent: false,
        share: { @MainActor shareData in
            guard
                let subjectEncoded = shareData.subject
                    .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
            else { return nil }
            // Better than sending just the link in the email body
            let messageEncoded = shareData.message ?? shareData.subject
            guard
                let bodyEncoded = "\(messageEncoded)\n\n\(shareData.shareURL.absoluteString)"
                    .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
            else { return nil }
            guard
                let url = URL(string: "mailto:?subject=\(subjectEncoded)&body=\(bodyEncoded)")
            else { throw SharingError.invalidURL }
            UIApplication.shared.open(url)
            return nil
        }
    )
    static let messages = ShareDestination(
        platformName: "ios_messages",
        name: L10n.FeatureShare.messages,
        icon: Image(systemName: "message"),
        needsVideo: false,
        presence: .systemEmail,
        requiresWatermark: false,
        isDownloadEvent: false,
        share: { @MainActor shareData in
            let messageBody = "\(shareData.subject)\n\n\(shareData.shareURL.absoluteString)"
            guard let composeView = try? SMSComposeView(messageBody: messageBody) else {
                log.telemetry.assertionFailure("Message composing is not supported on this device.")
                return nil
            }
            let composeVC = UIHostingController(rootView: composeView)
            UIApplication.shared.topViewController?.present(composeVC, animated: true, completion: nil)
            return nil
        }
    )
    static let download = ShareDestination(
        platformName: "ios_native_download",
        name: L10n.FeatureShare.save,
        icon: Image.Icon.downloadTray,
        needsVideo: true,
        presence: .external { _ in true },
        requiresWatermark: true,
        isDownloadEvent: true,
        share: { @MainActor shareData in
            guard
                let outputURL = shareData.downloadableURL
            else { throw SharingError.invalidURL }

            let videoDownloader = VideoDownloader()
            _ = try await videoDownloader.saveVideo(from: outputURL)

            return L10n.FeatureShare.downloadComplete
        }
    )
    static let tiktok = ShareDestination(
        platformName: "tiktok",
        name: L10n.FeatureShare.tiktok,
        icon: Image.Icon.tiktokShare,
        needsVideo: true,
        presence: .external { canOpenURL in
            guard let url = URL(string: "snssdk1180://") else { return false }
            return canOpenURL(url)
        },
        requiresWatermark: true,
        isDownloadEvent: false,
        share: { @MainActor shareData in
            guard let videoURL = shareData.downloadableURL else { throw SharingError.invalidURL }

            let videoDownloader = VideoDownloader()
            let localIdentifier = try await videoDownloader.saveVideo(from: videoURL)

            let baseUrl = BackendEnvironmentProvider.currentConfiguration().webEndpointHost
            let redirectURI = "https://\(baseUrl)/tiktok"

            let tikTokRequest = TikTokShareRequest(
                localIdentifiers: [localIdentifier],
                mediaType: .video,
                redirectURI: redirectURI
            )

            return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<String?, Error>) in
                tikTokRequest.send { response in
                    guard let shareResponse = response as? TikTokShareResponse else {
                        return continuation.resume(throwing: SharingError.saveVideoToPhotosLibraryFailed)
                    }
                    if shareResponse.errorCode == .noError {
                        continuation.resume(returning: nil)
                    } else {
                        continuation.resume(throwing: SharingError.saveVideoToPhotosLibraryFailed)
                    }
                }
            }
        }
    )
}

public extension ShareDestination {
    static func getShareDestinations() -> [ShareDestination] {
        var showTikTokShare: Bool {
            FeatureFlag.legacy.tiktokShare
        }

        var baseDestinations: [ShareDestination] = [.copyLink, .x, .instagram]
        let emailOrDownload: ShareDestination = .email

        // Save should be 4th in the list if it's a short
        // Otherwise, we show the email icon there instead
        baseDestinations.insert(emailOrDownload, at: 3)

        baseDestinations += [.facebook, .whatsApp, .messages]

        let filteredDestinations = baseDestinations.filter { $0.presence.isAvailable(UIApplication.shared.canOpenURL) }

        return filteredDestinations + [.system]
    }

    static func getCompactShareDestinations() -> [ShareDestination] {
        let destinations = getShareDestinations()
        return Array(destinations.prefix(4)) // Always include the first 4 destinations
    }
}
