import Foundation
import MacSyncShared
import Testing
@testable import INoteSync

final class FakeNoteApplier: NoteApplying, @unchecked Sendable {
    enum Call: Equatable {
        case create(NoteSendPayload)
        case update(NoteSendPayload)
        case delete(NoteSendPayload)
    }

    private(set) var calls: [Call] = []
    var nextResult: SendQueueApplyResult = .sent

    func create(_ payload: NoteSendPayload) async -> SendQueueApplyResult {
        calls.append(.create(payload))
        return nextResult
    }

    func update(_ payload: NoteSendPayload) async -> SendQueueApplyResult {
        calls.append(.update(payload))
        return nextResult
    }

    func delete(_ payload: NoteSendPayload) async -> SendQueueApplyResult {
        calls.append(.delete(payload))
        return nextResult
    }
}

@Suite("NoteSender dispatch")
@MainActor
struct SenderDispatchTests {
    private func item(_ action: String, _ payload: NoteSendPayload = NoteSendPayload()) -> PendingNoteSend {
        PendingNoteSend(id: "i-1", action: action, payload: payload, createdAt: "2026-05-13T00:00:00Z")
    }

    @Test("create_note dispatches to create")
    func dispatchCreate() async {
        let fake = FakeNoteApplier()
        let sender = NoteSender(applier: fake)
        let p = NoteSendPayload(folder: "Notes", name: "Hi", body: "<p>x</p>")
        let result = await sender.apply(item("create_note", p))
        guard case .sent = result else { Issue.record("expected .sent"); return }
        #expect(fake.calls == [.create(p)])
    }

    @Test("update_note dispatches to update")
    func dispatchUpdate() async {
        let fake = FakeNoteApplier()
        let sender = NoteSender(applier: fake)
        let p = NoteSendPayload(noteIdentifier: "id-1", body: "<p>new</p>")
        _ = await sender.apply(item("update_note", p))
        #expect(fake.calls == [.update(p)])
    }

    @Test("delete_note dispatches to delete")
    func dispatchDelete() async {
        let fake = FakeNoteApplier()
        let sender = NoteSender(applier: fake)
        let p = NoteSendPayload(noteIdentifier: "id-1")
        _ = await sender.apply(item("delete_note", p))
        #expect(fake.calls == [.delete(p)])
    }

    @Test("unknown action returns .failed without invoking applier")
    func unknownAction() async {
        let fake = FakeNoteApplier()
        let sender = NoteSender(applier: fake)
        let result = await sender.apply(item("frobnicate"))
        guard case .failed(let reason) = result else {
            Issue.record("expected .failed")
            return
        }
        #expect(reason.contains("unknown action"))
        #expect(fake.calls.isEmpty)
    }

    @Test("propagates .failed from applier")
    func propagatesFailure() async {
        let fake = FakeNoteApplier()
        fake.nextResult = .failed(reason: "boom")
        let sender = NoteSender(applier: fake)
        let result = await sender.apply(item("create_note", NoteSendPayload(name: "n")))
        guard case .failed(let reason) = result else {
            Issue.record("expected .failed")
            return
        }
        #expect(reason == "boom")
    }
}

@Suite("AppleScriptNoteApplier missing-field guards")
struct AppleScriptNoteApplierGuards {
    @Test("create without name returns .failed")
    func createMissingName() async {
        let applier = AppleScriptNoteApplier()
        let result = await applier.create(NoteSendPayload(body: "x"))
        guard case .failed(let reason) = result else {
            Issue.record("expected .failed"); return
        }
        #expect(reason.contains("name"))
    }

    @Test("update without noteIdentifier returns .failed")
    func updateMissingId() async {
        let applier = AppleScriptNoteApplier()
        let result = await applier.update(NoteSendPayload(body: "x"))
        guard case .failed(let reason) = result else {
            Issue.record("expected .failed"); return
        }
        #expect(reason.contains("noteIdentifier"))
    }

    @Test("update with id but no fields returns .failed")
    func updateNoFields() async {
        let applier = AppleScriptNoteApplier()
        let result = await applier.update(NoteSendPayload(noteIdentifier: "id-1"))
        guard case .failed(let reason) = result else {
            Issue.record("expected .failed"); return
        }
        #expect(reason.contains("no fields"))
    }

    @Test("delete without noteIdentifier returns .failed")
    func deleteMissingId() async {
        let applier = AppleScriptNoteApplier()
        let result = await applier.delete(NoteSendPayload())
        guard case .failed(let reason) = result else {
            Issue.record("expected .failed"); return
        }
        #expect(reason.contains("noteIdentifier"))
    }
}
