import Foundation
import ComposeApp
import Combine

/// ViewModel for residence management.
/// Observes DataManagerObservable for cached data.
/// Kicks off API calls that update DataManager, letting views react to cache updates.
@MainActor
class ResidenceViewModel: ObservableObject {
    private static var uiTestMockResidences: [ResidenceResponse] = []
    private static var uiTestNextResidenceId: Int = 1000

    // MARK: - Published Properties (from DataManager observation)
    @Published var myResidences: MyResidencesResponse?
    @Published var residences: [ResidenceResponse] = []
    @Published var totalSummary: TotalSummary?

    // MARK: - Local State
    @Published var selectedResidence: ResidenceResponse?
    @Published var isLoading: Bool = false
    @Published var errorMessage: String?
    @Published var isGeneratingReport: Bool = false
    @Published var reportMessage: String?

    // MARK: - Private Properties
    private var cancellables = Set<AnyCancellable>()
    private let dataManager: DataManagerObservable

    // MARK: - Initialization
    /// - Parameters:
    ///   - dataManager: Observable cache the VM subscribes to. Defaults
    ///     to the shared singleton. Tests and the parity-gallery pass a
    ///     fixture-backed instance instead.
    ///   - initialSelectedResidenceId: If set, the VM synchronously
    ///     resolves `selectedResidence` from the current `residences`
    ///     cache on init. Used by snapshot tests/previews so
    ///     `ResidenceDetailView` renders populated state on the first
    ///     composition frame, bypassing `getResidence(id:)`'s APILayer
    ///     round-trip which fails hermetically.
    init(
        dataManager: DataManagerObservable = .shared,
        initialSelectedResidenceId: Int32? = nil
    ) {
        self.dataManager = dataManager

        // Seed the VM's @Published mirrors synchronously from the current
        // cache values so snapshot tests and previews render populated
        // state without waiting for Combine's async dispatch. Production
        // runs hit this path too but the values are identical to what
        // the `.sink` closure would assign moments later on the main queue.
        self.myResidences = dataManager.myResidences
        self.residences = dataManager.residences
        self.totalSummary = dataManager.totalSummary

        if let id = initialSelectedResidenceId {
            // Try myResidences first (full response), then the top-level list.
            self.selectedResidence = dataManager.myResidences?.residences
                .first(where: { $0.id == id })
                ?? dataManager.residences.first(where: { $0.id == id })
        }

        // Observe injected DataManagerObservable for residence data
        dataManager.$myResidences
            .receive(on: DispatchQueue.main)
            .sink { [weak self] myResidences in
                self?.myResidences = myResidences
                // Clear loading state when data arrives
                if myResidences != nil {
                    self?.isLoading = false
                }
                // Auto-update selectedResidence if it exists in the updated list
                if let currentSelected = self?.selectedResidence,
                   let updatedResidence = myResidences?.residences.first(where: { $0.id == currentSelected.id }) {
                    self?.selectedResidence = updatedResidence
                }
            }
            .store(in: &cancellables)

        dataManager.$residences
            .receive(on: DispatchQueue.main)
            .sink { [weak self] residences in
                self?.residences = residences
                // Auto-update selectedResidence if it exists in the updated list
                if let currentSelected = self?.selectedResidence,
                   let updatedResidence = residences.first(where: { $0.id == currentSelected.id }) {
                    self?.selectedResidence = updatedResidence
                }
            }
            .store(in: &cancellables)

        dataManager.$totalSummary
            .receive(on: DispatchQueue.main)
            .sink { [weak self] summary in
                self?.totalSummary = summary
            }
            .store(in: &cancellables)
    }

    // MARK: - Public Methods

    /// Load summary - kicks off API call that updates DataManager
    func loadSummary(forceRefresh: Bool = false) {
        errorMessage = nil

        // Check if we have cached data and don't need to refresh
        if !forceRefresh && totalSummary != nil {
            return
        }

        isLoading = true

        // Kick off API call - DataManager will be updated, which updates DataManagerObservable
        Task {
            do {
                let result = try await APILayer.shared.getSummary(forceRefresh: forceRefresh)

                // Only handle errors - success updates DataManager automatically
                if let error = ApiResultBridge.error(from: result) {
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                }
                self.isLoading = false
            } catch {
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                self.isLoading = false
            }
        }
    }

    /// Load my residences - checks cache first, then fetches if needed
    func loadMyResidences(forceRefresh: Bool = false) {
        // Ensure lookups are initialized (may not be during onboarding)
        if !dataManager.lookupsInitialized {
            Task {
                _ = try? await APILayer.shared.initializeLookups()
            }
        }

        if UITestRuntime.shouldMockAuth {
            if Self.uiTestMockResidences.isEmpty || forceRefresh {
                if Self.uiTestMockResidences.isEmpty {
                    Self.uiTestMockResidences = [makeMockResidence(name: "Seed Residence")]
                }
            }
            myResidences = MyResidencesResponse(residences: Self.uiTestMockResidences)
            isLoading = false
            errorMessage = nil
            return
        }

        errorMessage = nil

        // Check if we have cached data and don't need to refresh
        if !forceRefresh && dataManager.myResidences != nil {
            // Data already available via observation, no API call needed
            return
        }

        isLoading = true

        // Kick off API call - DataManager will be updated, which updates DataManagerObservable,
        // which updates our @Published myResidences via the sink above
        Task {
            do {
                let result = try await APILayer.shared.getMyResidences(forceRefresh: forceRefresh)

                // Only handle errors - success updates DataManager automatically
                if let error = ApiResultBridge.error(from: result) {
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    self.isLoading = false
                }
            } catch {
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                self.isLoading = false
            }
        }
    }

    func getResidence(id: Int32, forceRefresh: Bool = false) {
        if UITestRuntime.shouldMockAuth {
            selectedResidence = Self.uiTestMockResidences.first(where: { $0.id == id })
            isLoading = false
            errorMessage = selectedResidence == nil ? "Residence not found" : nil
            return
        }

        isLoading = true
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.getResidence(id: id, forceRefresh: forceRefresh)

                if let success = result as? ApiResultSuccess<ResidenceResponse> {
                    self.selectedResidence = success.data
                    self.isLoading = false
                } else if let error = ApiResultBridge.error(from: result) {
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    self.isLoading = false
                } else {
                    self.errorMessage = "Failed to load residence"
                    self.isLoading = false
                }
            } catch {
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                self.isLoading = false
            }
        }
    }

    func createResidence(request: ResidenceCreateRequest, completion: @escaping (Bool) -> Void) {
        createResidence(request: request) { result in
            completion(result != nil)
        }
    }

    /// Creates a residence and returns the created residence on success
    func createResidence(request: ResidenceCreateRequest, completion: @escaping (ResidenceResponse?) -> Void) {
        if UITestRuntime.shouldMockAuth {
            let residence = makeMockResidence(
                name: request.name,
                streetAddress: request.streetAddress ?? "",
                city: request.city ?? "",
                stateProvince: request.stateProvince ?? "",
                postalCode: request.postalCode ?? ""
            )
            Self.uiTestMockResidences.append(residence)
            myResidences = MyResidencesResponse(residences: Self.uiTestMockResidences)
            isLoading = false
            errorMessage = nil
            completion(residence)
            return
        }

        isLoading = true
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.createResidence(request: request)

                await MainActor.run {
                    if let success = result as? ApiResultSuccess<ResidenceResponse> {
                        if let residence = success.data {
                            self.isLoading = false
                            completion(residence)
                        } else {
                            self.isLoading = false
                            completion(nil)
                        }
                    } else if let error = ApiResultBridge.error(from: result) {
                        self.errorMessage = ErrorMessageParser.parse(error.message)
                        self.isLoading = false
                        completion(nil)
                    } else {
                        self.isLoading = false
                        completion(nil)
                    }
                }
            } catch {
                await MainActor.run {
                    self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                    self.isLoading = false
                    completion(nil)
                }
            }
        }
    }

    func updateResidence(id: Int32, request: ResidenceCreateRequest, completion: @escaping (Bool) -> Void) {
        isLoading = true
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.updateResidence(id: id, request: request)

                if let success = result as? ApiResultSuccess<ResidenceResponse> {
                    self.selectedResidence = success.data
                    self.isLoading = false
                    // DataManager is updated by APILayer (including refreshMyResidences),
                    // which updates DataManagerObservable, which updates our @Published
                    // myResidences via Combine subscription
                    completion(true)
                } else if let error = ApiResultBridge.error(from: result) {
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    self.isLoading = false
                    completion(false)
                } else {
                    self.errorMessage = "Failed to update residence"
                    self.isLoading = false
                    completion(false)
                }
            } catch {
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                self.isLoading = false
                completion(false)
            }
        }
    }

    func generateTasksReport(residenceId: Int32, email: String? = nil) {
        isGeneratingReport = true
        reportMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.generateTasksReport(residenceId: residenceId, email: email)

                if let success = result as? ApiResultSuccess<GenerateReportResponse> {
                    self.reportMessage = success.data?.message ?? "Report generated, but no message returned."
                    self.isGeneratingReport = false
                } else if let error = ApiResultBridge.error(from: result) {
                    self.reportMessage = ErrorMessageParser.parse(error.message)
                    self.isGeneratingReport = false
                } else {
                    self.reportMessage = "Failed to generate report"
                    self.isGeneratingReport = false
                }
            } catch {
                self.reportMessage = ErrorMessageParser.parse(error.localizedDescription)
                self.isGeneratingReport = false
            }
        }
    }

    func clearError() {
        errorMessage = nil
    }

    func joinWithCode(code: String, completion: @escaping (Bool) -> Void) {
        isLoading = true
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.joinWithCode(code: code)

                if result is ApiResultSuccess<JoinResidenceResponse> {
                    self.isLoading = false
                    // APILayer updates DataManager with refreshMyResidences,
                    // which updates DataManagerObservable, which updates our
                    // @Published myResidences via Combine subscription
                    completion(true)
                } else if let error = ApiResultBridge.error(from: result) {
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    self.isLoading = false
                    completion(false)
                } else {
                    self.errorMessage = "Failed to join residence"
                    self.isLoading = false
                    completion(false)
                }
            } catch {
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                self.isLoading = false
                completion(false)
            }
        }
    }

    private func makeMockResidence(
        name: String,
        streetAddress: String = "",
        city: String = "",
        stateProvince: String = "",
        postalCode: String = ""
    ) -> ResidenceResponse {
        let id = Self.uiTestNextResidenceId
        Self.uiTestNextResidenceId += 1
        let now = "2026-02-20T00:00:00Z"
        return ResidenceResponse(
            id: Int32(id),
            ownerId: 1,
            owner: ResidenceUserResponse(id: 1, username: "testuser", email: "test@example.com", firstName: "UI", lastName: "Tester"),
            users: [],
            name: name,
            propertyTypeId: 1,
            propertyType: ResidenceType(id: 1, name: "House"),
            streetAddress: streetAddress,
            apartmentUnit: "",
            city: city,
            stateProvince: stateProvince,
            postalCode: postalCode,
            country: "USA",
            bedrooms: nil,
            bathrooms: nil,
            squareFootage: nil,
            lotSize: nil,
            yearBuilt: nil,
            description: "",
            purchaseDate: nil,
            purchasePrice: nil,
            isPrimary: false,
            isActive: true,
            overdueCount: 0,
            completionSummary: nil,
            heatingType: nil,
            coolingType: nil,
            waterHeaterType: nil,
            roofType: nil,
            hasPool: false,
            hasSprinklerSystem: false,
            hasSeptic: false,
            hasFireplace: false,
            hasGarage: false,
            hasBasement: false,
            hasAttic: false,
            exteriorType: nil,
            flooringPrimary: nil,
            landscapingType: nil,
            createdAt: now,
            updatedAt: now
        )
    }
}
