import Testing
import Foundation
import Contacts
import ContactsUI
@testable import nahbar

// MARK: - ContactPickerBridge – Mehrfachauswahl

@Suite("ContactPickerBridge – Mehrfachauswahl")
struct ContactPickerBridgeMultiTests {

    @Test("didSelect contacts → alle Kontakte an Callback")
    func multiSelectPassesAll() {
        let b = ContactPickerBridge()
        var received: [CNContact] = []
        b.pendingCallback = { received = $0 }

        let a = CNMutableContact(); a.givenName = "Alice"
        let c = CNMutableContact(); c.givenName = "Bob"
        b.contactPicker(CNContactPickerViewController(), didSelect: [a, c])

        #expect(received.count == 2)
        #expect(received[0].givenName == "Alice")
        #expect(received[1].givenName == "Bob")
    }

    @Test("didSelect contacts leer → leeres Array an Callback")
    func emptySelectionPassesEmpty() {
        let b = ContactPickerBridge()
        var received: [CNContact] = [CNMutableContact()] // vorbefüllt zum Unterscheiden
        b.pendingCallback = { received = $0 }
        b.contactPicker(CNContactPickerViewController(), didSelect: [] as [CNContact])

        #expect(received.isEmpty)
    }

    @Test("didSelect contact (singular) → in Array verpackt")
    func singularWrappedInArray() {
        let b = ContactPickerBridge()
        var received: [CNContact] = []
        b.pendingCallback = { received = $0 }

        let contact = CNMutableContact(); contact.givenName = "Einzeln"
        b.contactPicker(CNContactPickerViewController(), didSelect: contact)

        #expect(received.count == 1)
        #expect(received.first?.givenName == "Einzeln")
    }

    @Test("contactPickerDidCancel → kein Callback, pendingCallback nil")
    func cancelNoCallback() {
        let b = ContactPickerBridge()
        var callbackFired = false
        b.pendingCallback = { _ in callbackFired = true }
        b.contactPickerDidCancel(CNContactPickerViewController())

        #expect(!callbackFired)
        #expect(b.pendingCallback == nil)
    }

    @Test("pendingCallback wird nach didSelect auf nil gesetzt")
    func callbackClearedAfterSelect() {
        let b = ContactPickerBridge()
        b.pendingCallback = { _ in }
        b.contactPicker(CNContactPickerViewController(), didSelect: [] as [CNContact])

        #expect(b.pendingCallback == nil)
    }
}

// MARK: - ContactPickerBridge – Einzelauswahl (via presentSingle)

@Suite("ContactPickerBridge – Einzelauswahl")
struct ContactPickerBridgeSingleTests {

    @Test("presentSingle → didSelect contact → Callback mit diesem Kontakt")
    func singleSelectCallback() {
        let b = ContactPickerBridge()
        var received: CNContact? = nil
        // presentSingle setzt pendingCallback; presentPicker() schlägt in Tests stumm fehl
        b.presentSingle { received = $0 }

        let contact = CNMutableContact(); contact.givenName = "Anna"
        b.contactPicker(CNContactPickerViewController(), didSelect: contact)

        #expect(received?.givenName == "Anna")
    }

    @Test("presentSingle → didSelect contacts leer → kein Callback")
    func emptyContactsNoSingleCallback() {
        let b = ContactPickerBridge()
        var callbackFired = false
        b.presentSingle { _ in callbackFired = true }
        b.contactPicker(CNContactPickerViewController(), didSelect: [] as [CNContact])

        #expect(!callbackFired)
    }

    @Test("presentSingle → didSelect contacts → erstes Element an Callback")
    func singularFromPluralDelegate() {
        let b = ContactPickerBridge()
        var received: CNContact? = nil
        b.presentSingle { received = $0 }

        let first = CNMutableContact(); first.givenName = "Erster"
        let second = CNMutableContact(); second.givenName = "Zweiter"
        b.contactPicker(CNContactPickerViewController(), didSelect: [first, second])

        #expect(received?.givenName == "Erster")
    }

    @Test("contactPickerDidCancel → kein Callback")
    func cancelNoCallback() {
        let b = ContactPickerBridge()
        var callbackFired = false
        b.presentSingle { _ in callbackFired = true }
        b.contactPickerDidCancel(CNContactPickerViewController())

        #expect(!callbackFired)
    }
}

// MARK: - ContactImport – Mapping von CNContact

@Suite("ContactImport – Mapping von CNContact")
struct ContactImportTests {

    @Test("Vor- und Nachname → fullName korrekt")
    func fullNameMapping() {
        let contact = CNMutableContact()
        contact.givenName = "Anna"; contact.familyName = "Schmidt"
        #expect(ContactImport.from(contact).name == "Anna Schmidt")
    }

    @Test("Mittelname wird in Name einbezogen")
    func middleNameIncluded() {
        let contact = CNMutableContact()
        contact.givenName = "Max"; contact.middleName = "Otto"; contact.familyName = "Müller"
        #expect(ContactImport.from(contact).name == "Max Otto Müller")
    }

    @Test("Mittelname leer → kein doppeltes Leerzeichen")
    func emptyMiddleNameNoGap() {
        let contact = CNMutableContact()
        contact.givenName = "Max"; contact.familyName = "Müller"
        #expect(ContactImport.from(contact).name == "Max Müller")
    }

    @Test("Nur Vorname → kein Leerzeichen am Ende")
    func onlyFirstName() {
        let contact = CNMutableContact(); contact.givenName = "Cher"
        #expect(ContactImport.from(contact).name == "Cher")
    }

    @Test("Nur Nachname → kein Leerzeichen am Anfang")
    func onlyLastName() {
        let contact = CNMutableContact(); contact.familyName = "Prince"
        #expect(ContactImport.from(contact).name == "Prince")
    }

    @Test("Berufsbezeichnung und Firma werden kombiniert")
    func jobTitleAndOrgCombined() {
        let contact = CNMutableContact()
        contact.jobTitle = "Designer"; contact.organizationName = "ACME GmbH"
        #expect(ContactImport.from(contact).occupation == "Designer · ACME GmbH")
    }

    @Test("Nur Berufsbezeichnung ohne Firma → kein Trennzeichen")
    func jobTitleAloneNoDot() {
        let contact = CNMutableContact()
        contact.jobTitle = "Designer"
        #expect(ContactImport.from(contact).occupation == "Designer")
    }

    @Test("Firma als Fallback wenn kein Beruf")
    func orgNameFallback() {
        let contact = CNMutableContact(); contact.organizationName = "ACME GmbH"
        #expect(ContactImport.from(contact).occupation == "ACME GmbH")
    }

    @Test("Weder Beruf noch Firma → leerer String")
    func emptyOccupation() {
        #expect(ContactImport.from(CNMutableContact()).occupation == "")
    }

    @Test("Stadt und Land → location korrekt")
    func locationCityAndCountry() {
        let contact = CNMutableContact()
        let address = CNMutablePostalAddress()
        address.city = "Berlin"; address.country = "Deutschland"
        contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: address)]
        #expect(ContactImport.from(contact).location == "Berlin, Deutschland")
    }

    @Test("Stadt, Bundesstaat und Land → alle drei kombiniert")
    func locationWithState() {
        let contact = CNMutableContact()
        let address = CNMutablePostalAddress()
        address.city = "San Francisco"; address.state = "CA"; address.country = "USA"
        contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: address)]
        #expect(ContactImport.from(contact).location == "San Francisco, CA, USA")
    }

    @Test("Nur Stadt, kein Bundesstaat → kein leerer Eintrag")
    func locationCityOnly() {
        let contact = CNMutableContact()
        let address = CNMutablePostalAddress()
        address.city = "Wien"
        contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: address)]
        #expect(ContactImport.from(contact).location == "Wien")
    }

    @Test("Kein Ort → leerer String")
    func emptyLocation() {
        #expect(ContactImport.from(CNMutableContact()).location == "")
    }

    @Test("Vollständige deutsche Adresse: Straße + PLZ + Stadt + Land")
    func fullGermanAddress() {
        let contact = CNMutableContact()
        let address = CNMutablePostalAddress()
        address.street = "Musterstraße 12"
        address.postalCode = "10115"
        address.city = "Berlin"
        address.country = "Deutschland"
        contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: address)]
        #expect(ContactImport.from(contact).location == "Musterstraße 12, 10115 Berlin, Deutschland")
    }

    @Test("Vollständige US-Adresse: Straße + PLZ + Stadt + State + Land")
    func fullUSAddress() {
        let contact = CNMutableContact()
        let address = CNMutablePostalAddress()
        address.street = "1 Infinite Loop"
        address.postalCode = "95014"
        address.city = "Cupertino"
        address.state = "CA"
        address.country = "USA"
        contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: address)]
        #expect(ContactImport.from(contact).location == "1 Infinite Loop, 95014 Cupertino, CA, USA")
    }

    @Test("Mehrzeilige Straße: Zeilenumbrüche werden zu Komma-Leerzeichen")
    func multiLineStreet() {
        let contact = CNMutableContact()
        let address = CNMutablePostalAddress()
        address.street = "Musterstraße 12\nHinterhaus"
        address.city = "Hamburg"
        contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: address)]
        #expect(ContactImport.from(contact).location == "Musterstraße 12, Hinterhaus, Hamburg")
    }

    @Test("Nur PLZ ohne Stadt → PLZ als cityPart")
    func postalCodeOnly() {
        let contact = CNMutableContact()
        let address = CNMutablePostalAddress()
        address.postalCode = "10115"
        contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: address)]
        #expect(ContactImport.from(contact).location == "10115")
    }

    @Test("Geburtstag mit vollständigem Datum → wird übernommen")
    func birthdayFullDate() {
        let contact = CNMutableContact()
        contact.birthday = DateComponents(year: 1990, month: 6, day: 15)
        let result = ContactImport.from(contact)
        let cal = Calendar.current
        #expect(result.birthday != nil)
        #expect(cal.component(.year,  from: result.birthday!) == 1990)
        #expect(cal.component(.month, from: result.birthday!) == 6)
        #expect(cal.component(.day,   from: result.birthday!) == 15)
    }

    @Test("Geburtstag mit Jahr=1 → aktuelles Jahr wird verwendet")
    func birthdayYearOneReplacedWithCurrentYear() {
        let contact = CNMutableContact()
        contact.birthday = DateComponents(year: 1, month: 3, day: 8)
        let result = ContactImport.from(contact)
        let currentYear = Calendar.current.component(.year, from: Date())
        #expect(result.birthday != nil)
        #expect(Calendar.current.component(.year, from: result.birthday!) == currentYear)
    }

    @Test("Kein Geburtstag → nil")
    func noBirthday() {
        #expect(ContactImport.from(CNMutableContact()).birthday == nil)
    }

    @Test("Kein Foto → photoData ist nil")
    func noPhoto() {
        #expect(ContactImport.from(CNMutableContact()).photoData == nil)
    }
}

// MARK: - NahbarContact – Kontakt-Mapping

@Suite("NahbarContact – init(from: CNContact)")
struct NahbarContactMappingTests {

    @Test("E-Mail-Adressen werden übernommen")
    func emailAddressesImported() {
        let contact = CNMutableContact()
        contact.emailAddresses = [
            CNLabeledValue(label: CNLabelWork, value: "max@acme.de" as NSString),
            CNLabeledValue(label: CNLabelHome, value: "max@privat.de" as NSString)
        ]
        let nc = NahbarContact(from: contact)
        #expect(nc.emailAddresses == ["max@acme.de", "max@privat.de"])
    }

    @Test("Keine E-Mails → leeres Array")
    func noEmailAddresses() {
        let nc = NahbarContact(from: CNMutableContact())
        #expect(nc.emailAddresses.isEmpty)
    }

    @Test("Firma wird übernommen")
    func organizationImported() {
        let contact = CNMutableContact()
        contact.organizationName = "Muster GmbH"
        let nc = NahbarContact(from: contact)
        #expect(nc.organizationName == "Muster GmbH")
    }

    @Test("Keine Firma → leerer String")
    func noOrganization() {
        let nc = NahbarContact(from: CNMutableContact())
        #expect(nc.organizationName == "")
    }

    @Test("Codable round-trip mit neuen Feldern")
    func codableRoundTrip() throws {
        let original = NahbarContact(
            givenName: "Lena",
            familyName: "Koch",
            phoneNumbers: ["+49 30 123456"],
            emailAddresses: ["lena@example.com"],
            organizationName: "Tech AG"
        )
        let data = try JSONEncoder().encode(original)
        let decoded = try JSONDecoder().decode(NahbarContact.self, from: data)
        #expect(decoded.emailAddresses == ["lena@example.com"])
        #expect(decoded.organizationName == "Tech AG")
    }

    @Test("Rückwärtskompatibilität: altes JSON ohne neue Felder → Defaults")
    func backwardsCompatibility() throws {
        let oldJSON = """
        {"id":"00000000-0000-0000-0000-000000000001","givenName":"Alt","familyName":"Daten","phoneNumbers":[],"notes":""}
        """.data(using: .utf8)!
        let decoded = try JSONDecoder().decode(NahbarContact.self, from: oldJSON)
        #expect(decoded.emailAddresses.isEmpty)
        #expect(decoded.organizationName == "")
    }
}
