//
//  StackDexTests.swift
//  StackDexTests
//
//  Created by Matthias Meister on 18.04.26.
//

import Testing
@testable import StackDex
import UIKit

struct StackDexTests {

    @Test func cameraSessionStateBlocksStartUntilConfigurationCommits() {
        var state = CameraSessionState()

        #expect(state.canStartSession)

        state.beginConfiguration()
        #expect(!state.canStartSession)

        state.commitConfiguration()
        #expect(state.canStartSession)
    }

    @Test func cameraSessionStateTracksNestedConfigurationDepth() {
        var state = CameraSessionState()

        state.beginConfiguration()
        state.beginConfiguration()
        #expect(!state.canStartSession)

        state.commitConfiguration()
        #expect(!state.canStartSession)

        state.commitConfiguration()
        #expect(state.canStartSession)
    }

    @Test func heuristicExtractorFindsStructuredFields() {
        let extractor = CardTextHeuristicExtractor()
        let payload = OCRTextPayload(
            rawText: "Charizard\n120 HP\n4/102 Base Set\nHolo Rare",
            lines: [
                RecognizedTextLine(text: "Charizard", confidence: 0.95, normalizedBounds: CGRect(x: 0.1, y: 0.82, width: 0.4, height: 0.08)),
                RecognizedTextLine(text: "120 HP", confidence: 0.81, normalizedBounds: CGRect(x: 0.75, y: 0.83, width: 0.12, height: 0.05)),
                RecognizedTextLine(text: "4/102 Base Set", confidence: 0.92, normalizedBounds: CGRect(x: 0.2, y: 0.12, width: 0.4, height: 0.05)),
                RecognizedTextLine(text: "Holo Rare", confidence: 0.88, normalizedBounds: CGRect(x: 0.7, y: 0.1, width: 0.2, height: 0.05)),
            ],
            averageConfidence: 0.89
        )

        let draft = extractor.extract(payload: payload, source: .onDeviceOffline)

        #expect(draft.cardName == "Charizard")
        #expect(draft.cardNumber == "4/102")
        #expect(draft.setIdentifier == "Base Set")
        #expect(draft.rarity == CardRarity.holoRare.rawValue)
        #expect(draft.confidence == .high)
    }

    @Test func pipelineFallsBackToVisionWhenCloudIsStubbed() async throws {
        let monitor = TestNetworkStatusProvider(isOnline: true)
        let pipeline = CardRecognitionPipeline(
            networkStatusProvider: monitor,
            cloudOCRClient: StubCloudOCRClient(),
            fallbackOCR: TestOCRService(payload: OCRTextPayload(
                rawText: "Pikachu\n25/165\nScarlet & Violet\nRare",
                lines: [
                    RecognizedTextLine(text: "Pikachu", confidence: 0.95, normalizedBounds: CGRect(x: 0.1, y: 0.8, width: 0.3, height: 0.08)),
                    RecognizedTextLine(text: "25/165", confidence: 0.91, normalizedBounds: CGRect(x: 0.3, y: 0.15, width: 0.18, height: 0.05)),
                    RecognizedTextLine(text: "Scarlet & Violet", confidence: 0.87, normalizedBounds: CGRect(x: 0.25, y: 0.11, width: 0.4, height: 0.05)),
                    RecognizedTextLine(text: "Rare", confidence: 0.75, normalizedBounds: CGRect(x: 0.76, y: 0.1, width: 0.12, height: 0.04)),
                ],
                averageConfidence: 0.87
            ))
        )

        let session = try await pipeline.recognizeCard(in: TestImageFactory.makeImage())

        #expect(session.draft.source == .onDeviceFallback)
        #expect(session.draft.cardName == "Pikachu")
        #expect(session.draft.cardNumber == "25/165")
        #expect(session.draft.notes.contains(where: { $0.contains("Cloud OCR") }))
    }

}

private final class TestNetworkStatusProvider: NetworkStatusProviding {
    var isOnline: Bool

    init(isOnline: Bool) {
        self.isOnline = isOnline
    }

    func startMonitoring() {}
    func stopMonitoring() {}
}

private struct TestOCRService: CardTextRecognizing {
    let payload: OCRTextPayload

    func recognizeText(in image: PreparedImage) async throws -> OCRTextPayload {
        payload
    }
}

private enum TestImageFactory {
    static func makeImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: CGSize(width: 300, height: 420))
        return renderer.image { context in
            UIColor.white.setFill()
            context.fill(CGRect(x: 0, y: 0, width: 300, height: 420))
            UIColor.black.setStroke()
            context.cgContext.setLineWidth(6)
            context.cgContext.stroke(CGRect(x: 16, y: 16, width: 268, height: 388))
        }
    }
}
