//
//  AppleMailServiceTests.swift
//  oAITests
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen

import Testing
import Foundation
@testable import Confab

@Suite("AppleMailService descriptor decoding and formatting")
struct AppleMailServiceTests {

    // MARK: - decodeStringList

    private func makeListDescriptor(_ items: [String]) -> NSAppleEventDescriptor {
        let list = NSAppleEventDescriptor.list()
        for (index, item) in items.enumerated() {
            list.insert(NSAppleEventDescriptor(string: item), at: index + 1)
        }
        return list
    }

    @Test("decodeStringList decodes a flat list of strings")
    func decodesFlatList() {
        let descriptor = makeListDescriptor(["a", "b", "c"])
        #expect(AppleMailService.decodeStringList(descriptor) == ["a", "b", "c"])
    }

    @Test("decodeStringList returns empty array for an empty list")
    func decodesEmptyList() {
        let descriptor = makeListDescriptor([])
        #expect(AppleMailService.decodeStringList(descriptor) == [])
    }

    @Test("decodeStringList falls back to a single-element array for a bare string descriptor")
    func decodesBareString() {
        let descriptor = NSAppleEventDescriptor(string: "hello")
        #expect(AppleMailService.decodeStringList(descriptor) == ["hello"])
    }

    // MARK: - parseSearchResults

    @Test("parseSearchResults decodes a list of 7-field message records")
    func parsesSearchResults() {
        let record1 = makeListDescriptor(["msg-1", "Receipt from Elkjøp", "orders@elkjop.no", "Monday, 1 January 2026", "INBOX", "Personal", "1"])
        let record2 = makeListDescriptor(["msg-2", "Another subject", "someone@example.com", "Tuesday, 2 January 2026", "INBOX", "Work", "0"])
        let outer = NSAppleEventDescriptor.list()
        outer.insert(record1, at: 1)
        outer.insert(record2, at: 2)

        let results = AppleMailService.parseSearchResults(from: outer)
        #expect(results.count == 2)
        #expect(results[0].messageId == "msg-1")
        #expect(results[0].subject == "Receipt from Elkjøp")
        #expect(results[0].from == "orders@elkjop.no")
        #expect(results[0].mailbox == "INBOX")
        #expect(results[0].account == "Personal")
        #expect(results[0].attachmentCount == "1")
        #expect(results[1].messageId == "msg-2")
        #expect(results[1].attachmentCount == "0")
    }

    @Test("parseSearchResults returns empty array for an empty outer list")
    func parsesEmptySearchResults() {
        let outer = NSAppleEventDescriptor.list()
        #expect(AppleMailService.parseSearchResults(from: outer) == [])
    }

    @Test("parseSearchResults skips malformed records with too few fields")
    func skipsMalformedRecords() {
        let goodRecord = makeListDescriptor(["msg-1", "Subject", "from@example.com", "date", "INBOX", "Account", "0"])
        let badRecord = makeListDescriptor(["only", "three", "fields"])
        let outer = NSAppleEventDescriptor.list()
        outer.insert(badRecord, at: 1)
        outer.insert(goodRecord, at: 2)

        let results = AppleMailService.parseSearchResults(from: outer)
        #expect(results.count == 1)
        #expect(results[0].messageId == "msg-1")
    }

    // MARK: - mapAppleScriptError

    @Test("mapAppleScriptError maps -1743 to an Automation-permission message")
    func mapsNotAuthorizedError() {
        let message = AppleMailService.mapAppleScriptError(number: -1743, message: "Not authorized")
        #expect(message.contains("not authorized"))
        #expect(message.contains("Automation"))
    }

    @Test("mapAppleScriptError maps -600 to a Mail-not-available message")
    func mapsApplicationNotRunningError() {
        let message = AppleMailService.mapAppleScriptError(number: -600, message: "Application isn't running")
        #expect(message.contains("not available"))
    }

    @Test("mapAppleScriptError falls back to a generic formatted message for unknown error numbers")
    func mapsGenericError() {
        let message = AppleMailService.mapAppleScriptError(number: -1234, message: "Something odd happened")
        #expect(message.contains("-1234"))
        #expect(message.contains("Something odd happened"))
    }

    // MARK: - parseArgs

    @Test("parseArgs decodes a JSON arguments string")
    func parsesArgsJSON() {
        let args = AppleMailService.parseArgs("{\"account\":\"work@example.com\",\"subject_contains\":\"Elkjøp\"}")
        #expect(args["account"] as? String == "work@example.com")
        #expect(args["subject_contains"] as? String == "Elkjøp")
    }

    @Test("parseArgs returns empty dictionary for invalid JSON")
    func parsesInvalidArgsJSON() {
        let args = AppleMailService.parseArgs("not json")
        #expect(args.isEmpty)
    }

    // MARK: - hasSearchCriteria

    @Test("hasSearchCriteria is false when every criterion is nil or empty")
    func rejectsUnscopedSearch() {
        #expect(AppleMailService.hasSearchCriteria(subjectContains: nil, fromContains: "", bodyContains: nil, since: nil, before: "") == false)
    }

    @Test("hasSearchCriteria is true when any single criterion is non-empty")
    func acceptsScopedSearch() {
        #expect(AppleMailService.hasSearchCriteria(subjectContains: "Elkjøp", fromContains: nil, bodyContains: nil, since: nil, before: nil) == true)
        #expect(AppleMailService.hasSearchCriteria(subjectContains: nil, fromContains: nil, bodyContains: nil, since: "2026-01-01", before: nil) == true)
    }

    // MARK: - approvalSummary

    @Test("approvalSummary describes mail_list_accounts")
    func summarizesListAccounts() {
        let summary = AppleMailService.shared.approvalSummary(forTool: "mail_list_accounts", arguments: "{}")
        #expect(summary == "List your Mail.app accounts")
    }

    @Test("approvalSummary includes account and subject scope for mail_search")
    func summarizesSearch() {
        let summary = AppleMailService.shared.approvalSummary(
            forTool: "mail_search",
            arguments: "{\"account\":\"work@example.com\",\"subject_contains\":\"Elkjøp\"}"
        )
        #expect(summary.contains("work@example.com"))
        #expect(summary.contains("Elkjøp"))
    }

    @Test("approvalSummary for mail_search with no scope still returns a sensible label")
    func summarizesUnscopedSearch() {
        let summary = AppleMailService.shared.approvalSummary(forTool: "mail_search", arguments: "{}")
        #expect(summary == "Search Mail")
    }

    @Test("approvalSummary describes mail_save_attachment with name and destination")
    func summarizesSaveAttachment() {
        let summary = AppleMailService.shared.approvalSummary(
            forTool: "mail_save_attachment",
            arguments: "{\"attachment_name\":\"receipt.pdf\",\"dest_path\":\"/tmp/receipt.pdf\"}"
        )
        #expect(summary.contains("receipt.pdf"))
        #expect(summary.contains("/tmp/receipt.pdf"))
    }

    @Test("approvalSummary falls back to a generic label for an unknown tool name")
    func summarizesUnknownTool() {
        let summary = AppleMailService.shared.approvalSummary(forTool: "mail_bogus", arguments: "{}")
        #expect(summary == "Perform action: mail_bogus")
    }

    // MARK: - mapAutomationPermissionStatus

    @Test("mapAutomationPermissionStatus maps noErr (0) to granted")
    func mapsGrantedStatus() {
        #expect(AppleMailService.mapAutomationPermissionStatus(0) == .granted)
    }

    @Test("mapAutomationPermissionStatus maps -1743 to denied")
    func mapsDeniedStatus() {
        #expect(AppleMailService.mapAutomationPermissionStatus(-1743) == .denied)
    }

    @Test("mapAutomationPermissionStatus maps -1744 (would require consent) and other codes to notDetermined")
    func mapsNotDeterminedStatus() {
        #expect(AppleMailService.mapAutomationPermissionStatus(-1744) == .notDetermined)
        #expect(AppleMailService.mapAutomationPermissionStatus(-9999) == .notDetermined)
    }

    // MARK: - classifyRequestOutcome

    @Test("classifyRequestOutcome maps success to granted regardless of elapsed time")
    func classifiesSuccessAsGranted() {
        #expect(AppleMailService.classifyRequestOutcome(succeeded: true, elapsedSeconds: 0.001) == .granted)
        #expect(AppleMailService.classifyRequestOutcome(succeeded: true, elapsedSeconds: 5.0) == .granted)
    }

    @Test("classifyRequestOutcome treats an implausibly fast failure as the suspected platform bug")
    func classifiesFastFailureAsSuspectedBug() {
        #expect(AppleMailService.classifyRequestOutcome(succeeded: false, elapsedSeconds: 0.009) == .suspectedPlatformBug)
        #expect(AppleMailService.classifyRequestOutcome(succeeded: false, elapsedSeconds: 0.0) == .suspectedPlatformBug)
    }

    @Test("classifyRequestOutcome treats a slower failure as a real denial")
    func classifiesSlowFailureAsDenied() {
        #expect(AppleMailService.classifyRequestOutcome(succeeded: false, elapsedSeconds: 1.5) == .denied)
    }

    @Test("classifyRequestOutcome threshold is right at the boundary")
    func classifiesThresholdBoundary() {
        #expect(AppleMailService.classifyRequestOutcome(succeeded: false, elapsedSeconds: AppleMailService.suspectedBugThreseholdSeconds) == .denied)
        #expect(AppleMailService.classifyRequestOutcome(succeeded: false, elapsedSeconds: AppleMailService.suspectedBugThreseholdSeconds - 0.001) == .suspectedPlatformBug)
    }
}
