import UIKit

struct TheObjectUploadResponse: Decodable {
    let ok: Bool
    let captureId: String?
    let jobId: String?
}

struct TheObjectStatusResponse: Decodable {
    let ok: Bool
    let captureId: String?
    let status: String
    let ready: Bool
    let doneVideos: Int?
    let neededVideos: Int?
    let assets: TheObjectLiveAssets?
    let orientation: String?
    let glow: Bool?
    let statusNote: String?
}

struct TheObjectRecentResponse: Decodable {
    let ok: Bool
    let captures: [TheObjectStatusResponse]
}

struct TheObjectVideoVariant: Decodable {
    let url: String
    let badge: String
    let provider: String
}

struct TheObjectLiveAssets: Decodable {
    let originalImage: String?
    let heroImage: String?
    let realExplodedImage: String?
    let unrealExplodedImage: String?
    let neatifyImage: String?
    let secretDetailImage: String?
    let videos: [String: String]
    // videoVariants: filmName → { "S": { url, badge, provider }, "K": { url, badge, provider } }
    let videoVariants: [String: [String: TheObjectVideoVariant]]?
}

extension TheObjectLiveAssets {
    /// Welche Generator-Badges (z.B. ["S", "K"]) hat Film film_01_original_to_hero?
    func availableBadges(forFilm filmName: String) -> [String] {
        guard let variants = videoVariants?[filmName] else { return [] }
        return variants.keys.sorted()
    }

    /// URL für einen bestimmten Film + Badge. Fallback auf videos-Dict wenn nötig.
    func url(forFilm filmName: String, badge: String) -> String? {
        if let variant = videoVariants?[filmName]?[badge] { return variant.url }
        return videos["\(filmName)_\(badge)"] ?? videos[filmName]
    }
}

private struct NormalizedUploadImage {
    let data: Data
    let ratio: String
    let orientation: String
}

enum TheObjectIngestClient {
    private static var serverBases: [URL] {
        var values: [String] = []
        if let value = Bundle.main.object(forInfoDictionaryKey: "TheObjectServerBaseURL") as? String,
           !value.isEmpty {
            values.append(value)
        }
        values.append("http://Minimac.local:8797")
        values.append("http://192.168.0.213:8797")
        var seen = Set<String>()
        return values.compactMap { value in
            guard let url = URL(string: value), seen.insert(url.absoluteString).inserted else { return nil }
            return url
        }
    }

    private static func endpoint(_ path: String, base: URL) -> URL {
        base.appendingPathComponent(path)
    }

    static func endpointURL(_ path: String) -> URL {
        endpoint(path, base: serverBases[0])
    }

    static var debugServerBaseString: String {
        serverBases.map(\.absoluteString).joined(separator: ",")
    }

    static func upload(image: UIImage, captureId: UUID) async throws -> TheObjectUploadResponse {
        guard let uploadImage = normalizedJPEGData(from: image) else {
            throw URLError(.cannotDecodeContentData)
        }

        var lastError: Error?
        for base in serverBases {
            do {
                var request = URLRequest(url: endpoint("upload", base: base))
                request.httpMethod = "POST"
                request.setValue("image/jpeg", forHTTPHeaderField: "Content-Type")
                request.setValue(captureId.uuidString, forHTTPHeaderField: "X-TheObject-Capture-ID")
                request.setValue(uploadImage.orientation, forHTTPHeaderField: "X-TheObject-Orientation")
                request.setValue(uploadImage.ratio, forHTTPHeaderField: "X-TheObject-Ratio")
                request.timeoutInterval = 45

                let (responseData, response) = try await URLSession.shared.upload(for: request, from: uploadImage.data)
                guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
                    throw URLError(.badServerResponse)
                }
                theObjectCheck("upload_source=\(base.absoluteString)")
                return try JSONDecoder().decode(TheObjectUploadResponse.self, from: responseData)
            } catch {
                lastError = error
                theObjectCheck("upload_source_failed base=\(base.absoluteString) error=\(error.localizedDescription)")
            }
        }
        throw lastError ?? URLError(.cannotConnectToHost)
    }

    static func status(captureId: UUID) async throws -> TheObjectStatusResponse {
        var lastError: Error?
        for base in serverBases {
            do {
                var components = URLComponents(url: endpoint("status", base: base), resolvingAgainstBaseURL: false)!
                components.queryItems = [URLQueryItem(name: "captureId", value: captureId.uuidString)]
                var request = URLRequest(url: components.url!)
                request.timeoutInterval = 8
                let (data, response) = try await URLSession.shared.data(for: request)
                guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
                    throw URLError(.badServerResponse)
                }
                return try JSONDecoder().decode(TheObjectStatusResponse.self, from: data)
            } catch {
                lastError = error
            }
        }
        throw lastError ?? URLError(.cannotConnectToHost)
    }

    static func recent() async throws -> TheObjectRecentResponse {
        var lastError: Error?
        for base in serverBases {
            for path in ["objects/recent", "recent"] {
                do {
                    let response = try await recent(from: endpoint(path, base: base))
                    theObjectCheck("recent_source=\(base.absoluteString)/\(path) count=\(response.captures.count) ids=\(response.captures.compactMap { $0.captureId }.joined(separator: ","))")
                    return response
                } catch {
                    lastError = error
                    theObjectCheck("recent_source_failed base=\(base.absoluteString) path=/\(path) error=\(error.localizedDescription)")
                }
            }
        }
        throw lastError ?? URLError(.cannotConnectToHost)
    }

    private static func recent(from url: URL) async throws -> TheObjectRecentResponse {
        var request = URLRequest(url: url)
        request.timeoutInterval = 6
        let (data, response) = try await URLSession.shared.data(for: request)
        guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
            throw URLError(.badServerResponse)
        }
        return try JSONDecoder().decode(TheObjectRecentResponse.self, from: data)
    }

    private static func normalizedJPEGData(from image: UIImage) -> NormalizedUploadImage? {
        let format = UIGraphicsImageRendererFormat()
        format.scale = 1
        format.opaque = true

        let isPortrait = image.size.height > image.size.width
        let targetRatio = isPortrait ? "9:16" : "16:9"
        let orientation = isPortrait ? "portrait" : "landscape"
        let targetSize = isPortrait ? CGSize(width: 2160, height: 3840) : CGSize(width: 3840, height: 2160)
        let scale = min(targetSize.width / image.size.width, targetSize.height / image.size.height)
        let fitted = CGSize(width: image.size.width * scale, height: image.size.height * scale)
        let imageRect = CGRect(
            x: (targetSize.width - fitted.width) / 2,
            y: (targetSize.height - fitted.height) / 2,
            width: fitted.width,
            height: fitted.height
        )

        let renderer = UIGraphicsImageRenderer(size: targetSize, format: format)
        let data = renderer.jpegData(withCompressionQuality: 0.94) { context in
            UIColor.black.setFill()
            context.fill(CGRect(origin: .zero, size: targetSize))
            UIBezierPath(roundedRect: imageRect, cornerRadius: 72).addClip()
            image.draw(in: imageRect)
        }
        return NormalizedUploadImage(data: data, ratio: targetRatio, orientation: orientation)
    }
}
