import Foundation
import Contacts
import OSLog
import SwiftUI

private let contactLogger = Logger(subsystem: "nahbar", category: "ContactStore")

// MARK: - NahbarContact

/// A contact selected during onboarding, persisted locally as JSON.
/// No contact data is ever sent to any server.
struct NahbarContact: Identifiable, Codable, Equatable {
    var id: UUID
    var givenName: String
    var familyName: String
    var phoneNumbers: [String]
    /// E-Mail-Adressen aus dem Adressbuch (alle Labels).
    var emailAddresses: [String]
    /// Firma oder Organisation des Kontakts.
    var organizationName: String
    var notes: String
    /// Formatierte Postanschrift (Straße, PLZ Stadt, Land).
    var location: String
    /// Original CNContact identifier for stable matching against the system address book.
    var cnIdentifier: String?

    init(
        id: UUID = UUID(),
        givenName: String,
        familyName: String,
        phoneNumbers: [String] = [],
        emailAddresses: [String] = [],
        organizationName: String = "",
        notes: String = "",
        location: String = "",
        cnIdentifier: String? = nil
    ) {
        self.id = id
        self.givenName = givenName
        self.familyName = familyName
        self.phoneNumbers = phoneNumbers
        self.emailAddresses = emailAddresses
        self.organizationName = organizationName
        self.notes = notes
        self.location = location
        self.cnIdentifier = cnIdentifier
    }

    /// Initialises from a CNContact. No data is persisted at this point.
    init(from contact: CNContact) {
        self.id = UUID()
        self.givenName = contact.givenName
        self.familyName = contact.familyName
        self.phoneNumbers = contact.phoneNumbers.map { $0.value.stringValue }
        self.emailAddresses = contact.emailAddresses.map { $0.value as String }
        self.organizationName = contact.organizationName
        // CNContactNoteKey requires a special entitlement – omitted intentionally.
        self.notes = ""
        self.location = ContactImport.from(contact).location
        self.cnIdentifier = contact.identifier
    }

    // MARK: - Codable (rückwärtskompatibel)
    // Neue Felder mit decodeIfPresent lesen, damit bestehende NahbarContacts.json
    // ohne diese Felder weiterhin geladen werden können.

    enum CodingKeys: String, CodingKey {
        case id, givenName, familyName, phoneNumbers, emailAddresses, organizationName, notes, location, cnIdentifier
    }

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        id               = try c.decode(UUID.self,   forKey: .id)
        givenName        = try c.decode(String.self, forKey: .givenName)
        familyName       = try c.decode(String.self, forKey: .familyName)
        phoneNumbers     = try c.decodeIfPresent([String].self, forKey: .phoneNumbers)     ?? []
        emailAddresses   = try c.decodeIfPresent([String].self, forKey: .emailAddresses)   ?? []
        organizationName = try c.decodeIfPresent(String.self,   forKey: .organizationName) ?? ""
        notes            = try c.decodeIfPresent(String.self,   forKey: .notes)            ?? ""
        location         = try c.decodeIfPresent(String.self,   forKey: .location)         ?? ""
        cnIdentifier     = try c.decodeIfPresent(String.self,   forKey: .cnIdentifier)
    }
    // encode(to:) wird vom Compiler synthetisiert, da alle Felder Encodable sind.

    var fullName: String {
        [givenName, familyName].filter { !$0.isEmpty }.joined(separator: " ")
    }

    var initials: String {
        let g = givenName.prefix(1).uppercased()
        let f = familyName.prefix(1).uppercased()
        if !g.isEmpty && !f.isEmpty { return g + f }
        return g.isEmpty ? (f.isEmpty ? "?" : String(f)) : String(g)
    }
}

// MARK: - ContactStoring Protocol

/// Abstraction over the contact persistence layer – injectable for testing.
protocol ContactStoring: AnyObject {
    var contacts: [NahbarContact] { get }
    func save(_ contacts: [NahbarContact]) throws
    func load() throws -> [NahbarContact]
}

// MARK: - ContactStore

/// Persists onboarding contacts as a JSON file in Application Support.
/// All data stays local – no network requests are made.
final class ContactStore: ContactStoring {
    static let shared = ContactStore()

    private(set) var contacts: [NahbarContact] = []

    private var storeURL: URL {
        let dir = FileManager.default
            .urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
        return dir.appendingPathComponent("NahbarContacts.json")
    }

    private init() {
        contacts = (try? load()) ?? []
    }

    func save(_ newContacts: [NahbarContact]) throws {
        let encoded = try JSONEncoder().encode(newContacts)
        let dir = storeURL.deletingLastPathComponent()
        if !FileManager.default.fileExists(atPath: dir.path) {
            try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
        }
        try encoded.write(to: storeURL, options: .atomic)
        self.contacts = newContacts
        contactLogger.info("ContactStore: \(newContacts.count) Kontakt(e) gespeichert")
    }

    func reset() {
        try? FileManager.default.removeItem(at: storeURL)
        contacts = []
    }

    func load() throws -> [NahbarContact] {
        guard FileManager.default.fileExists(atPath: storeURL.path) else { return [] }
        let data = try Data(contentsOf: storeURL)
        return try JSONDecoder().decode([NahbarContact].self, from: data)
    }
}

// MARK: - Environment Key

private struct ContactStoreKey: EnvironmentKey {
    static let defaultValue: any ContactStoring = ContactStore.shared
}

extension EnvironmentValues {
    var contactStore: any ContactStoring {
        get { self[ContactStoreKey.self] }
        set { self[ContactStoreKey.self] = newValue }
    }
}
