import SwiftUI
import Foundation
import OSLog

private let theObjectCheckLogger = Logger(subsystem: "dev.beachorchestra.theobjectfresh", category: "check")

func theObjectCheck(_ message: String) {
    print("THE_OBJECT_CHECK \(message)")
    theObjectCheckLogger.notice("THE_OBJECT_CHECK \(message, privacy: .public)")
    Task.detached(priority: .background) {
        await TheObjectDiagnostics.send(message)
    }
}

private enum TheObjectDiagnostics {
    static func send(_ message: String) async {
        var request = URLRequest(url: TheObjectIngestClient.endpointURL("diagnostics"))
        request.httpMethod = "POST"
        request.timeoutInterval = 2
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        let payload: [String: String] = [
            "message": message,
            "sentAt": ISO8601DateFormatter().string(from: Date())
        ]
        request.httpBody = try? JSONSerialization.data(withJSONObject: payload)
        _ = try? await URLSession.shared.data(for: request)
    }
}

enum ObjectPhase: String {
    case reality
    case hero
    case exploded
    case detail
}

enum ObjectDisplayOrientation: Hashable {
    case portrait
    case landscape
}

struct ObjectSet: Identifiable, Hashable {
    let id: String
    let title: String
    let displayOrientation: ObjectDisplayOrientation
    let originalImage: String
    let heroImage: String
    let explodedImage: String
    let realityToHero: String
    let heroScrub: String
    let heroToExploded: String
    let explodedScrub: String
    var neatifyImage: String? = nil
    var explodedToNeatify: String? = nil
    var unrealExplodedImage: String? = nil
    var heroToUnrealExploded: String? = nil
    var unrealExplodedScrub: String? = nil
    var secretDetailImage: String? = nil
    var secretExplodedToSecretDetail: String? = nil
    let heroOrbitInverted: Bool = false
    let explodedOrbitInverted: Bool = false

    var realityToHeroReverse: String { reverseName(for: realityToHero) }
    var heroScrubReverse: String { reverseName(for: heroScrub) }
    var heroToExplodedReverse: String { reverseName(for: heroToExploded) }
    var explodedScrubReverse: String { reverseName(for: explodedScrub) }

    func orbitVideos(for phase: ObjectPhase) -> (forward: String, reverse: String) {
        switch phase {
        case .hero:
            return heroOrbitInverted ? (heroScrubReverse, heroScrub) : (heroScrub, heroScrubReverse)
        case .exploded:
            return explodedOrbitInverted ? (explodedScrubReverse, explodedScrub) : (explodedScrub, explodedScrubReverse)
        case .reality:
            return ("", "")
        case .detail:
            return ("", "")
        }
    }

    func branchVariant(real: Bool) -> ObjectSet {
        guard !real else { return self }
        return ObjectSet(
            id: id + "-unreal",
            title: title,
            displayOrientation: displayOrientation,
            originalImage: originalImage,
            heroImage: heroImage,
            explodedImage: unrealExplodedImage ?? explodedImage,
            realityToHero: realityToHero,
            heroScrub: heroScrub,
            heroToExploded: heroToUnrealExploded ?? heroToExploded,
            explodedScrub: unrealExplodedScrub ?? explodedScrub,
            neatifyImage: secretDetailImage ?? neatifyImage,
            explodedToNeatify: secretExplodedToSecretDetail ?? explodedToNeatify,
            unrealExplodedImage: unrealExplodedImage,
            heroToUnrealExploded: heroToUnrealExploded,
            unrealExplodedScrub: unrealExplodedScrub,
            secretDetailImage: secretDetailImage,
            secretExplodedToSecretDetail: secretExplodedToSecretDetail
        )
    }

    private func reverseName(for value: String) -> String {
        if let url = URL(string: value) {
            if url.scheme == "http" || url.scheme == "https" {
                return reverseURLString(for: url) ?? value
            }
            if url.scheme == "file" {
                return reverseFileURL(for: url).absoluteString
            }
        }
        return value + "__reverse"
    }

    private func reverseURLString(for url: URL) -> String? {
        guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return nil }
        if var items = components.queryItems,
           let index = items.firstIndex(where: { $0.name == "path" }),
           let path = items[index].value {
            items[index].value = reversePath(path)
            components.queryItems = items
            return components.url?.absoluteString
        }
        let path = url.path
        guard path.hasSuffix(".mp4") else { return nil }
        components.path = reversePath(path)
        return components.url?.absoluteString
    }

    private func reversePath(_ path: String) -> String {
        let url = URL(fileURLWithPath: path)
        let ext = url.pathExtension
        guard !ext.isEmpty else { return path + "__reverse" }
        return String(path.dropLast(ext.count + 1)) + "__reverse." + ext
    }

    private func reverseFileURL(for url: URL) -> URL {
        let ext = url.pathExtension
        let base = url.deletingPathExtension()
        guard !ext.isEmpty else {
            return URL(fileURLWithPath: base.path + "__reverse")
        }
        return URL(fileURLWithPath: base.path + "__reverse").appendingPathExtension(ext)
    }
}

struct QueuedCapture: Identifiable {
    let id: UUID
    let remoteCaptureId: String?
    var image: UIImage
    let isPortrait: Bool
    let startedAt = Date()
    var uploadState: QueuedCaptureState = .uploading
    var result: ObjectSet?
    var glows: Bool = false
    var isPartialResult: Bool = false

    init(id: UUID = UUID(), remoteCaptureId: String? = nil, image: UIImage, isPortrait: Bool, uploadState: QueuedCaptureState = .uploading, result: ObjectSet? = nil, glows: Bool = false, isPartialResult: Bool = false) {
        self.id = id
        self.remoteCaptureId = remoteCaptureId
        self.image = image
        self.isPortrait = isPortrait
        self.uploadState = uploadState
        self.result = result
        self.glows = glows
        self.isPartialResult = isPartialResult
    }
}

enum QueuedCaptureState: Equatable {
    case uploading
    case heroQueued(String?)
    case caching
    case failed

    var debugText: String {
        switch self {
        case .uploading:
            return "Foto geht zum Mac"
        case .heroQueued(let jobId):
            if let jobId, !jobId.isEmpty {
                return "Hero laeuft: \(jobId)"
            }
            return "Hero laeuft"
        case .caching:
            return "Laedt lokal"
        case .failed:
            return "Wartet auf Motor"
        }
    }
}

actor LocalObjectCache {
    static let shared = LocalObjectCache()

    private let fileManager = FileManager.default

    func cachedObjectSet(_ objectSet: ObjectSet) async throws -> ObjectSet {
        let root = try cacheRoot(for: objectSet.id)
        let originalImage = try await cachedAsset(objectSet.originalImage, in: root, named: "00_original")
        let heroImage = try await cachedAsset(objectSet.heroImage, in: root, named: "01_hero")
        let explodedImage = try await cachedAsset(objectSet.explodedImage, in: root, named: "02_exploded_real")
        let neatifyImage: String?
        if let value = objectSet.neatifyImage {
            neatifyImage = try await cachedAsset(value, in: root, named: "04_neatify_real")
        } else {
            neatifyImage = nil
        }
        let unrealExplodedImage: String?
        if let value = objectSet.unrealExplodedImage {
            unrealExplodedImage = try await cachedAsset(value, in: root, named: "03_exploded_unreal")
        } else {
            unrealExplodedImage = nil
        }

        let realityToHero = try await cachedVideoPair(objectSet.realityToHero, in: root, named: "01_reality_to_hero")
        let heroScrub = try await cachedVideoPair(objectSet.heroScrub, in: root, named: "04a_hero_360")
        let heroToExploded = try await cachedVideoPair(objectSet.heroToExploded, in: root, named: "02_hero_to_exploded_real")
        let explodedScrub = try await cachedVideoPair(objectSet.explodedScrub, in: root, named: "04b_exploded_real_360")
        let explodedToNeatify: String?
        if let value = objectSet.explodedToNeatify {
            explodedToNeatify = try await cachedVideoPair(value, in: root, named: "05_real_to_neatify")
        } else {
            explodedToNeatify = nil
        }
        let heroToUnrealExploded: String?
        if let value = objectSet.heroToUnrealExploded {
            heroToUnrealExploded = try await cachedVideoPair(value, in: root, named: "03_hero_to_exploded_unreal")
        } else {
            heroToUnrealExploded = nil
        }
        let unrealExplodedScrub: String?
        if let value = objectSet.unrealExplodedScrub {
            unrealExplodedScrub = try await cachedVideoPair(value, in: root, named: "04c_exploded_unreal_360")
        } else {
            unrealExplodedScrub = nil
        }
        let secretDetailImage: String?
        if let value = objectSet.secretDetailImage {
            secretDetailImage = try await cachedAsset(value, in: root, named: "05_secret_detail")
        } else {
            secretDetailImage = nil
        }
        let secretExplodedToSecretDetail: String?
        if let value = objectSet.secretExplodedToSecretDetail {
            secretExplodedToSecretDetail = try await cachedVideoPair(value, in: root, named: "06_secret_to_secret_detail")
        } else {
            secretExplodedToSecretDetail = nil
        }

        return ObjectSet(
            id: objectSet.id,
            title: objectSet.title,
            displayOrientation: objectSet.displayOrientation,
            originalImage: originalImage,
            heroImage: heroImage,
            explodedImage: explodedImage,
            realityToHero: realityToHero,
            heroScrub: heroScrub,
            heroToExploded: heroToExploded,
            explodedScrub: explodedScrub,
            neatifyImage: neatifyImage,
            explodedToNeatify: explodedToNeatify,
            unrealExplodedImage: unrealExplodedImage,
            heroToUnrealExploded: heroToUnrealExploded,
            unrealExplodedScrub: unrealExplodedScrub,
            secretDetailImage: secretDetailImage,
            secretExplodedToSecretDetail: secretExplodedToSecretDetail
        )
    }

    private func cacheRoot(for id: String) throws -> URL {
        let base = try fileManager.url(
            for: .applicationSupportDirectory,
            in: .userDomainMask,
            appropriateFor: nil,
            create: true
        )
        let root = base.appendingPathComponent("TheObject/Objects", isDirectory: true)
            .appendingPathComponent(safeFileName(id), isDirectory: true)
        try fileManager.createDirectory(at: root, withIntermediateDirectories: true)
        return root
    }

    private func cachedVideoPair(_ value: String, in root: URL, named name: String) async throws -> String {
        let forward = try await cachedAsset(value, in: root, named: name, forcedExtension: videoExtension(for: value))
        if let reverse = reverseAssetName(for: value) {
            _ = try await cachedAsset(reverse, in: root, named: name + "__reverse", forcedExtension: videoExtension(for: reverse))
        }
        return forward
    }

    private func cachedAsset(_ value: String, in root: URL, named name: String, forcedExtension: String? = nil) async throws -> String {
        guard let remoteURL = URL(string: value), remoteURL.scheme == "http" || remoteURL.scheme == "https" else {
            return value
        }
        let ext = forcedExtension ?? remoteURL.pathExtension.nonEmpty ?? "bin"
        let destination = root.appendingPathComponent(safeFileName(name)).appendingPathExtension(ext)
        let sourceMarker = destination.appendingPathExtension("source")
        if fileExistsAndIsNonEmpty(destination),
           (try? String(contentsOf: sourceMarker, encoding: .utf8)) == value {
            return destination.absoluteString
        }

        let (temporaryURL, response) = try await URLSession.shared.download(from: remoteURL)
        guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
            throw URLError(.badServerResponse)
        }
        let partial = destination.appendingPathExtension("partial")
        try? fileManager.removeItem(at: partial)
        try? fileManager.removeItem(at: destination)
        try fileManager.moveItem(at: temporaryURL, to: partial)
        try fileManager.moveItem(at: partial, to: destination)
        try value.write(to: sourceMarker, atomically: true, encoding: .utf8)
        return destination.absoluteString
    }

    private func reverseAssetName(for value: String) -> String? {
        guard let url = URL(string: value) else { return nil }
        if url.scheme == "http" || url.scheme == "https" {
            return reverseURLString(for: url)
        }
        if url.scheme == "file" {
            return reverseFileURL(for: url).absoluteString
        }
        return value + "__reverse"
    }

    private func reverseURLString(for url: URL) -> String? {
        guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return nil }
        if var items = components.queryItems,
           let index = items.firstIndex(where: { $0.name == "path" }),
           let path = items[index].value {
            items[index].value = reversePath(path)
            components.queryItems = items
            return components.url?.absoluteString
        }
        guard url.path.hasSuffix(".mp4") else { return nil }
        components.path = reversePath(url.path)
        return components.url?.absoluteString
    }

    private func reverseFileURL(for url: URL) -> URL {
        let ext = url.pathExtension
        let base = url.deletingPathExtension()
        guard !ext.isEmpty else {
            return URL(fileURLWithPath: base.path + "__reverse")
        }
        return URL(fileURLWithPath: base.path + "__reverse").appendingPathExtension(ext)
    }

    private func reversePath(_ path: String) -> String {
        guard path.hasSuffix(".mp4") else { return path + "__reverse" }
        return String(path.dropLast(4)) + "__reverse.mp4"
    }

    private func videoExtension(for value: String) -> String {
        guard let url = URL(string: value) else { return "mp4" }
        if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
           let path = components.queryItems?.first(where: { $0.name == "path" })?.value {
            let ext = URL(fileURLWithPath: path).pathExtension
            return ext.isEmpty ? "mp4" : ext
        }
        let ext = url.pathExtension
        return ext.isEmpty ? "mp4" : ext
    }

    private func fileExistsAndIsNonEmpty(_ url: URL) -> Bool {
        guard let values = try? url.resourceValues(forKeys: [.fileSizeKey]),
              let size = values.fileSize else { return false }
        return size > 0
    }

    private func safeFileName(_ value: String) -> String {
        let allowed = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-_"))
        return String(value.unicodeScalars.map { allowed.contains($0) ? Character($0) : "_" })
    }
}

private extension String {
    var nonEmpty: String? { isEmpty ? nil : self }
}

private func liveObjectSet(id: String, title: String, displayOrientation: ObjectDisplayOrientation = .landscape) -> ObjectSet {
    let base = "LiveObjects/\(id)"
    return ObjectSet(
        id: id,
        title: title,
        displayOrientation: displayOrientation,
        originalImage: "\(base)/original",
        heroImage: "\(base)/hero",
        explodedImage: "\(base)/exploded_real",
        realityToHero: "\(base)/reality_to_hero",
        heroScrub: "\(base)/hero_360",
        heroToExploded: "\(base)/hero_to_exploded_real",
        explodedScrub: "\(base)/exploded_real_360",
        unrealExplodedImage: "\(base)/exploded_unreal",
        heroToUnrealExploded: "\(base)/hero_to_exploded_unreal",
        unrealExplodedScrub: "\(base)/exploded_unreal_360"
    )
}

let demoObjectSets: [ObjectSet] = [
    liveObjectSet(id: "croc_wan_0155", title: "WAN 1:55"),
    liveObjectSet(id: "croc_veo_lite_0218", title: "VEO LITE 2:18"),
    ObjectSet(
        id: "salz",
        title: "Salzfass",
        displayOrientation: .portrait,
        originalImage: "theobject_salz_original",
        heroImage: "theobject_salz_hero",
        explodedImage: "theobject_salz_exploded_real",
        realityToHero: "theobject_salz_reality_to_hero",
        heroScrub: "theobject_salz_hero_360",
        heroToExploded: "theobject_salz_hero_to_exploded_real",
        explodedScrub: "theobject_salz_exploded_360_real",
        unrealExplodedImage: "theobject_salz_exploded_unreal",
        heroToUnrealExploded: "theobject_salz_hero_to_exploded_unreal",
        unrealExplodedScrub: "theobject_salz_exploded_360_unreal"
    ),
    ObjectSet(
        id: "computer",
        title: "Computer",
        displayOrientation: .landscape,
        originalImage: "theobject_computer_original",
        heroImage: "theobject_computer_hero",
        explodedImage: "theobject_computer_exploded_real",
        realityToHero: "theobject_computer_reality_to_hero",
        heroScrub: "theobject_computer_hero_360",
        heroToExploded: "theobject_computer_hero_to_exploded_real",
        explodedScrub: "theobject_computer_exploded_360_real",
        unrealExplodedImage: "theobject_computer_exploded_unreal",
        heroToUnrealExploded: "theobject_computer_hero_to_exploded_unreal",
        unrealExplodedScrub: "theobject_computer_exploded_360_unreal"
    ),
    ObjectSet(
        id: "koffer",
        title: "Koffer",
        displayOrientation: .landscape,
        originalImage: "theobject_koffer_original",
        heroImage: "theobject_koffer_hero",
        explodedImage: "theobject_koffer_exploded_real",
        realityToHero: "theobject_koffer_reality_to_hero",
        heroScrub: "theobject_koffer_hero_360",
        heroToExploded: "theobject_koffer_hero_to_exploded_real",
        explodedScrub: "theobject_koffer_exploded_360_real",
        unrealExplodedImage: "theobject_koffer_exploded_unreal",
        heroToUnrealExploded: "theobject_koffer_hero_to_exploded_unreal",
        unrealExplodedScrub: "theobject_koffer_exploded_360_unreal"
    ),
    ObjectSet(
        id: "hase",
        title: "Hase",
        displayOrientation: .portrait,
        originalImage: "hase-original",
        heroImage: "hase-hero",
        explodedImage: "hase-exploded-real",
        realityToHero: "hase__01-hero-hold",
        heroScrub: "hase__02-hero-360-eased",
        heroToExploded: "hase__03-hero-to-exploded-real",
        explodedScrub: "hase__04-exploded-360-real",
        unrealExplodedImage: "hase-exploded-poetic",
        heroToUnrealExploded: "hase__03-hero-to-exploded-poetic",
        unrealExplodedScrub: "hase__04-exploded-360-poetic"
    ),
    ObjectSet(
        id: "hookbulb",
        title: "Gluehbirne",
        displayOrientation: .landscape,
        originalImage: "LiveObjects/hookbulb/original",
        heroImage: "LiveObjects/hookbulb/hero",
        explodedImage: "LiveObjects/hookbulb/exploded_real",
        realityToHero: "LiveObjects/hookbulb/reality_to_hero",
        heroScrub: "LiveObjects/hookbulb/hero_360",
        heroToExploded: "LiveObjects/hookbulb/hero_to_exploded_real",
        explodedScrub: "LiveObjects/hookbulb/exploded_real_360"
    ),
    ObjectSet(
        id: "trashcan",
        title: "Muelleimer",
        displayOrientation: .landscape,
        originalImage: "LiveObjects/trashcan/original",
        heroImage: "LiveObjects/trashcan/hero",
        explodedImage: "LiveObjects/trashcan/exploded_real",
        realityToHero: "LiveObjects/trashcan/reality_to_hero",
        heroScrub: "LiveObjects/trashcan/hero_360",
        heroToExploded: "LiveObjects/trashcan/hero_to_exploded_real",
        explodedScrub: "LiveObjects/trashcan/exploded_real_360"
    ),
    ObjectSet(
        id: "parkingsign",
        title: "Parkingschild",
        displayOrientation: .landscape,
        originalImage: "parkingsign-a",
        heroImage: "parkingsign-hero-poster",
        explodedImage: "parkingsign-exploded-poster",
        realityToHero: "parkingsign__01-reality-to-hero__appfix",
        heroScrub: "parkingsign__02-hero-scrub__appfix",
        heroToExploded: "parkingsign__03-hero-to-exploded__appfix",
        explodedScrub: "parkingsign__04-exploded-scrub__appfix"
    ),
    liveObjectSet(id: "salzstreuer_wolken", title: "Salzstreuer Wolken", displayOrientation: .portrait),
    liveObjectSet(id: "iphone_decke", title: "iPhone Decke", displayOrientation: .portrait),
    liveObjectSet(id: "trams_nebel", title: "Trams Nebel"),
    liveObjectSet(id: "pizza", title: "Pizza"),
    liveObjectSet(id: "mann_mantel", title: "Mann im Mantel", displayOrientation: .portrait)
]
