//
//  SnapshotTesting+Testing.swift
//  OpenSwiftUIUITests

import SnapshotTesting
import Testing
import TestingHost
import Foundation

let defaultSize = CGSize(width: 200, height: 200)

private let defaultSnapshotReferenceRoot: String = {
    URL(fileURLWithPath: #filePath)
        .deletingLastPathComponent()
        .deletingLastPathComponent()
        .deletingLastPathComponent()
        .appendingPathComponent("ReferenceImages")
        .path
}()

let snapshotReferenceDirectory: String = {
    #if os(macOS)
    let os = "macOS"
    #elseif os(iOS)
    #if targetEnvironment(simulator)
    let os = "iOS_Simulator"
    #else
    let os = "iOS"
    #endif
    #else
    #error("Unsupported UI test platform")
    #endif
    let version = ProcessInfo.processInfo.operatingSystemVersion
    let osVersion = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
    let configuredRoot = ProcessInfo.processInfo.environment["SNAPSHOT_REFERENCE_DIR"]
    let referenceRoot: String
    if let configuredRoot, !configuredRoot.isEmpty, !configuredRoot.contains("$(") {
        referenceRoot = configuredRoot
    } else {
        referenceRoot = defaultSnapshotReferenceRoot
    }
    let directory = referenceRoot + "/\(os)/\(osVersion)"
    print("SNAPSHOT_REFERENCE_DIR: \(directory)")
    return directory
}()

#if os(macOS)
import AppKit

extension Snapshotting where Value == NSViewController, Format == NSImage {
    /// drawHierarchyInKeyWindow is iOS only parameter, here for compatibility
    static func image(
        drawHierarchyInKeyWindow: Bool,
        precision: Float = 1,
        perceptualPrecision: Float = 1,
        size: CGSize? = nil,
    ) -> Snapshotting {
        .image(precision: precision, perceptualPrecision: perceptualPrecision, size: size)
    }
}
#endif

func openSwiftUIAssertSnapshot<V: View>(
    of value: @autoclosure () -> V,
    drawHierarchyInKeyWindow: Bool = false,
    precision: Float = 1,
    perceptualPrecision: Float = 1,
    size: CGSize = defaultSize,
    named name: String? = nil,
    record recording: SnapshotTestingConfiguration.Record? = shouldRecord,
    timeout: TimeInterval = 5,
    fileID: StaticString = #fileID,
    file filePath: StaticString = #filePath,
    testName: String = #function,
    line: UInt = #line,
    column: UInt = #column
) {
    openSwiftUIAssertSnapshot(
        of: PlatformHostingController(rootView: value()),
        as: .image(drawHierarchyInKeyWindow: drawHierarchyInKeyWindow, precision: precision, perceptualPrecision: perceptualPrecision, size: size),
        named: (name.map { ".\($0)" } ?? "") + "\(Int(size.width))x\(Int(size.height))",
        record: recording,
        timeout: timeout,
        fileID: fileID,
        file: filePath,
        testName: testName,
        line: line,
        column: column
    )
}

func openSwiftUIAssertSnapshot<V: View>(
    of value: @autoclosure () -> V,
    as snapshotting: Snapshotting<PlatformViewController, PlatformImage>,
    named name: String? = nil,
    record recording: SnapshotTestingConfiguration.Record? = shouldRecord,
    timeout: TimeInterval = 5,
    fileID: StaticString = #fileID,
    file filePath: StaticString = #filePath,
    testName: String = #function,
    line: UInt = #line,
    column: UInt = #column
) {
    openSwiftUIAssertSnapshot(
        of: PlatformHostingController(rootView: value()),
        as: snapshotting,
        named: name,
        record: recording,
        timeout: timeout,
        fileID: fileID,
        file: filePath,
        testName: testName,
        line: line,
        column: column
    )
}

func openSwiftUIAssertSnapshot<V: View, Format>(
    of value: @autoclosure () -> V,
    as snapshotting: Snapshotting<PlatformViewController, Format>,
    named name: String? = nil,
    record recording: SnapshotTestingConfiguration.Record? = shouldRecord,
    timeout: TimeInterval = 5,
    fileID: StaticString = #fileID,
    file filePath: StaticString = #filePath,
    testName: String = #function,
    line: UInt = #line,
    column: UInt = #column
) {
    openSwiftUIAssertSnapshot(
        of: PlatformHostingController(rootView: value()),
        as: snapshotting,
        named: name,
        record: recording,
        timeout: timeout,
        fileID: fileID,
        file: filePath,
        testName: testName,
        line: line,
        column: column
    )
}

// FIXME: Should remove controller in name
func openSwiftUIControllerAssertSnapshot<V: PlatformViewController, Format>(
    of value: @autoclosure () -> V,
    as snapshotting: Snapshotting<PlatformViewController, Format>,
    named name: String? = nil,
    record recording: SnapshotTestingConfiguration.Record? = shouldRecord,
    timeout: TimeInterval = 5,
    fileID: StaticString = #fileID,
    file filePath: StaticString = #filePath,
    testName: String = #function,
    line: UInt = #line,
    column: UInt = #column
) {
    openSwiftUIAssertSnapshot(
        of: value(),
        as: snapshotting,
        named: name,
        record: recording,
        timeout: timeout,
        fileID: fileID,
        file: filePath,
        testName: testName,
        line: line,
        column: column
    )
}

// FIXME: Should be internal, private due to conflict infer
private func openSwiftUIAssertSnapshot<Value, Format>(
    of value: @autoclosure () -> Value,
    as snapshotting: Snapshotting<Value, Format>,
    named name: String? = nil,
    record recording: SnapshotTestingConfiguration.Record? = shouldRecord,
    timeout: TimeInterval = 5,
    fileID: StaticString = #fileID,
    file filePath: StaticString = #filePath,
    testName: String = #function,
    line: UInt = #line,
    column: UInt = #column
) {
    let snapshotDirectory = snapshotReferenceDirectory + "/" + fileID.description
    let failure = verifySnapshot(
        of: value(),
        as: snapshotting,
        named: name,
        record: recording,
        snapshotDirectory: snapshotDirectory,
        timeout: timeout,
        fileID: fileID,
        file: filePath,
        testName: testName,
        line: line,
        column: column
    )
    guard let message = failure else { return }
    Issue.record(
        Comment(rawValue: message),
        sourceLocation: SourceLocation(
            fileID: fileID.description,
            filePath: filePath.description,
            line: Int(line),
            column: Int(column)
        )
    )
}

// MARK: - Animation

func openSwiftUIAssertAnimationSnapshot<V: AnimationTestView>(
    of value: @autoclosure () -> V,
    precision: Float = 1,
    perceptualPrecision: Float = 1,
    size: CGSize = defaultSize,
    record recording: SnapshotTestingConfiguration.Record? = shouldRecord,
    timeout: TimeInterval = 5,
    fileID: StaticString = #fileID,
    file filePath: StaticString = #filePath,
    testName: String = #function,
    line: UInt = #line,
    column: UInt = #column
) {
    let vc = AnimationDebugController(value())
    let model = V.model
    var intervals = model.intervals
    intervals.insert(.zero, at: 0)
    intervals.enumerated().forEach { (index, interval) in
        switch index {
        case 0:
            break
        case 1:
            vc.advance(interval: .zero)
            vc.advance(interval: .zero)
            vc.advance(interval: .zero)
            vc.advance(interval: interval)
        default:
            vc.advance(interval: interval)
        }
        openSwiftUIAssertSnapshot(
            of: vc,
            as: .image(precision: precision, perceptualPrecision: perceptualPrecision, size: size),
            named: "\(index)_\(model.intervals.count).\(Int(size.width))x\(Int(size.height))",
            record: recording,
            timeout: timeout,
            fileID: fileID,
            file: filePath,
            testName: testName,
            line: line,
            column: column
        )
    }
}
