import Foundation
import Libbox
import SwiftUI
import UniformTypeIdentifiers

public extension Profile {
    func toContent() throws -> LibboxProfileContent {
        let content = LibboxProfileContent()
        content.name = name
        switch type {
        case .local, .icloud:
            content.type = LibboxProfileTypeLocal
        case .remote:
            content.type = LibboxProfileTypeRemote
        }
        content.config = try read()
        if type == .remote {
            content.remotePath = remoteURL!
        }
        if type == .remote {
            content.autoUpdate = autoUpdate
            content.autoUpdateInterval = autoUpdateInterval
            if let lastUpdated {
                content.lastUpdated = Int64(lastUpdated.timeIntervalSince1970 * 1000)
            }
        }
        return content
    }

    func encodedContentDataAsync() async throws -> Data {
        let name = name
        let type = type
        let remoteURL = remoteURL
        let autoUpdate = autoUpdate
        let autoUpdateInterval = autoUpdateInterval
        let lastUpdated = lastUpdated

        let config = try await readAsync()
        return try await BlockingIO.run {
            let content = LibboxProfileContent()
            content.name = name
            switch type {
            case .local, .icloud:
                content.type = LibboxProfileTypeLocal
            case .remote:
                content.type = LibboxProfileTypeRemote
            }
            content.config = config
            if type == .remote {
                content.remotePath = remoteURL!
                content.autoUpdate = autoUpdate
                content.autoUpdateInterval = autoUpdateInterval
                if let lastUpdated {
                    content.lastUpdated = Int64(lastUpdated.timeIntervalSince1970 * 1000)
                }
            }
            guard let encoded = content.encode() else {
                throw NSError(domain: "Profile", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to encode profile"])
            }
            return encoded
        }
    }

    func generateShareFileAsync() async throws -> URL {
        let name = name
        let type = type
        let remoteURL = remoteURL
        let autoUpdate = autoUpdate
        let autoUpdateInterval = autoUpdateInterval
        let lastUpdated = lastUpdated

        let config = try await readAsync()
        return try await BlockingIO.run {
            let content = LibboxProfileContent()
            content.name = name
            switch type {
            case .local, .icloud:
                content.type = LibboxProfileTypeLocal
            case .remote:
                content.type = LibboxProfileTypeRemote
            }
            content.config = config
            if type == .remote {
                content.remotePath = remoteURL!
                content.autoUpdate = autoUpdate
                content.autoUpdateInterval = autoUpdateInterval
                if let lastUpdated {
                    content.lastUpdated = Int64(lastUpdated.timeIntervalSince1970 * 1000)
                }
            }
            return try content.generateShareFile()
        }
    }

    func generateJSONShareFileAsync(name: String) async throws -> URL {
        let filename = name
        let config = try await readAsync()
        return try await BlockingIO.run {
            try config.generateShareFile(name: filename)
        }
    }
}

public func dateFromTimestamp(_ timestamp: Int64) -> Date {
    if timestamp > 100_000_000_000 {
        return Date(timeIntervalSince1970: Double(timestamp) / 1000)
    } else {
        return Date(timeIntervalSince1970: Double(timestamp))
    }
}

@available(iOS 16.0, macOS 13.0, *)
extension Profile: Transferable {
    public static var transferRepresentation: some TransferRepresentation {
        ProxyRepresentation { profile in
            try TypedProfile(profile.toContent())
        }
    }
}

public extension LibboxProfileContent {
    static func from(_ data: Data) throws -> LibboxProfileContent {
        var error: NSError?
        let content = LibboxDecodeProfileContent(data, &error)
        if let error {
            throw error
        }
        return content!
    }

    @discardableResult
    func importProfile() async throws -> Profile {
        let name = name
        let type = type
        let config = config
        let remotePath = remotePath
        let autoUpdate = autoUpdate
        let autoUpdateInterval = autoUpdateInterval
        let lastUpdated = lastUpdated
        let nextProfileID = try await ProfileManager.nextID()
        let profileConfigDirectory = FilePath.sharedDirectory.appendingPathComponent("configs", isDirectory: true)
        let profileConfig = profileConfigDirectory.appendingPathComponent("config_\(nextProfileID).json")
        var lastUpdatedAt: Date?
        if lastUpdated > 0 {
            lastUpdatedAt = dateFromTimestamp(lastUpdated)
        }
        try await BlockingIO.run {
            try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
            try config.write(to: profileConfig, atomically: true, encoding: .utf8)
        }
        let uniqueProfileName = try await ProfileManager.uniqueName(name)
        let profile = Profile(
            name: uniqueProfileName,
            type: ProfileType(rawValue: Int(type))!,
            path: profileConfig.relativePath,
            remoteURL: remotePath,
            autoUpdate: autoUpdate,
            autoUpdateInterval: autoUpdateInterval,
            lastUpdated: lastUpdatedAt
        )
        try await ProfileManager.create(profile)
        await SharedPreferences.selectedProfileID.set(profile.mustID)
        return profile
    }

    func generateShareFile() throws -> URL {
        let shareDirectory = FilePath.cacheDirectory.appendingPathComponent("share", isDirectory: true)
        try FileManager.default.createDirectory(at: shareDirectory, withIntermediateDirectories: true)
        let shareFile = shareDirectory.appendingPathComponent("\(name).bpf")
        try encode()!.write(to: shareFile)
        return shareFile
    }
}

public extension String {
    func generateShareFile(name: String) throws -> URL {
        let shareDirectory = FilePath.cacheDirectory.appendingPathComponent("share", isDirectory: true)
        try FileManager.default.createDirectory(at: shareDirectory, withIntermediateDirectories: true)
        let shareFile = shareDirectory.appendingPathComponent(name)
        try write(to: shareFile, atomically: true, encoding: .utf8)
        return shareFile
    }
}

@available(iOS 16.0, macOS 13.0, *)
public struct TypedProfile: Transferable, Codable {
    public let content: LibboxProfileContent
    public init(_ content: LibboxProfileContent) {
        self.content = content
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let data = try container.decode(Data.self)
        try self.init(.from(data))
    }

    public static var transferRepresentation: some TransferRepresentation {
        FileRepresentation(contentType: .profile) { typed in
            try SentTransferredFile(typed.content.generateShareFile(), allowAccessingOriginalFile: true)
        } importing: { received in
            try TypedProfile(.from(Data(contentsOf: received.file)))
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(content.encode()!)
    }
}

public extension UTType {
    static let profile = UTType(exportedAs: AppConfiguration.profileUTType)
}

#if !os(tvOS)

    // MARK: - FileDocument for Export

    public struct ProfileExportDocument: FileDocument {
        public static var readableContentTypes: [UTType] {
            [.profile]
        }

        public let data: Data
        public let filename: String

        public init(content: LibboxProfileContent) throws {
            guard let encoded = content.encode() else {
                throw NSError(domain: "ProfileExportDocument", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to encode profile"])
            }
            data = encoded
            filename = "\(content.name).bpf"
        }

        public init(configuration: ReadConfiguration) throws {
            guard let data = configuration.file.regularFileContents else {
                throw CocoaError(.fileReadCorruptFile)
            }
            self.data = data
            filename = "profile.bpf"
        }

        public func fileWrapper(configuration _: WriteConfiguration) throws -> FileWrapper {
            FileWrapper(regularFileWithContents: data)
        }
    }

    public struct ProfileJSONExportDocument: FileDocument {
        public static var readableContentTypes: [UTType] {
            [.json]
        }

        public let content: String
        public let filename: String

        public init(jsonContent: String, name: String) {
            content = jsonContent
            filename = "\(name).json"
        }

        public init(configuration: ReadConfiguration) throws {
            guard let data = configuration.file.regularFileContents,
                  let content = String(data: data, encoding: .utf8)
            else {
                throw CocoaError(.fileReadCorruptFile)
            }
            self.content = content
            filename = "profile.json"
        }

        public func fileWrapper(configuration _: WriteConfiguration) throws -> FileWrapper {
            guard let data = content.data(using: .utf8) else {
                throw CocoaError(.fileWriteInapplicableStringEncoding)
            }
            return FileWrapper(regularFileWithContents: data)
        }
    }

    public struct ProfileAnyExportDocument: FileDocument {
        public static var readableContentTypes: [UTType] {
            [.data, .json]
        }

        public let data: Data
        public let filename: String
        public let contentType: UTType

        public init(data: Data, filename: String, contentType: UTType) {
            self.data = data
            self.filename = filename
            self.contentType = contentType
        }

        public init(profile: ProfileExportDocument) {
            data = profile.data
            filename = profile.filename
            contentType = .data
        }

        public init(json: ProfileJSONExportDocument) {
            data = json.content.data(using: .utf8) ?? Data()
            filename = json.filename
            contentType = .json
        }

        public init(configuration: ReadConfiguration) throws {
            guard let data = configuration.file.regularFileContents else {
                throw CocoaError(.fileReadCorruptFile)
            }
            self.data = data
            filename = "profile"
            contentType = .data
        }

        public func fileWrapper(configuration _: WriteConfiguration) throws -> FileWrapper {
            FileWrapper(regularFileWithContents: data)
        }
    }
#endif
