import XCTest

/// Multi-user residence sharing integration tests.
///
/// Tests the full sharing lifecycle using the real local API:
/// 1. User A creates a residence and generates a share code
/// 2. User B joins using the share code
/// 3. Both users create tasks on the shared residence
/// 4. Both users can see all tasks
///
/// These tests run entirely via API (no app launch needed for most steps)
/// with a final UI verification that the shared residence and tasks appear.
final class MultiUserSharingTests: XCTestCase {

    private var userA: TestSession!
    private var userB: TestSession!
    private var cleanerA: TestDataCleaner!
    private var cleanerB: TestDataCleaner!

    override func setUpWithError() throws {
        continueAfterFailure = false

        guard TestAccountAPIClient.isBackendReachable() else {
            throw XCTSkip("Local backend is not reachable at \(TestAccountAPIClient.baseURL)")
        }

        // Create two fresh verified accounts
        let runId = UUID().uuidString.prefix(6)
        guard let a = TestAccountAPIClient.createVerifiedAccount(
            username: "sharer_a_\(runId)",
            email: "sharer_a_\(runId)@test.com",
            password: "TestPass123!"
        ) else {
            XCTFail("Could not create User A"); return
        }
        userA = a
        cleanerA = TestDataCleaner(token: a.token)

        guard let b = TestAccountAPIClient.createVerifiedAccount(
            username: "sharer_b_\(runId)",
            email: "sharer_b_\(runId)@test.com",
            password: "TestPass123!"
        ) else {
            XCTFail("Could not create User B"); return
        }
        userB = b
        cleanerB = TestDataCleaner(token: b.token)
    }

    override func tearDownWithError() throws {
        // Clean up any resources tracked during tests (handles mid-test failures)
        cleanerA?.cleanAll()
        cleanerB?.cleanAll()
        try super.tearDownWithError()
    }

    // MARK: - Full Sharing Flow

    func test01_fullSharingLifecycle() throws {
        // ── Step 1: User A creates a residence ──
        let residenceName = "Shared Home \(Int(Date().timeIntervalSince1970))"
        guard let residence = TestAccountAPIClient.createResidence(
            token: userA.token,
            name: residenceName
        ) else {
            XCTFail("User A should be able to create a residence")
            return
        }
        let residenceId = residence.id

        // ── Step 2: User A generates a share code ──
        guard let shareCode = TestAccountAPIClient.generateShareCode(
            token: userA.token,
            residenceId: residenceId
        ) else {
            XCTFail("User A should be able to generate a share code")
            return
        }
        XCTAssertEqual(shareCode.code.count, 6, "Share code should be 6 characters")
        XCTAssertTrue(shareCode.isActive, "Share code should be active")

        // ── Step 3: User B joins using the share code ──
        guard let joinResponse = TestAccountAPIClient.joinWithCode(
            token: userB.token,
            code: shareCode.code
        ) else {
            XCTFail("User B should be able to join with the share code")
            return
        }
        XCTAssertEqual(joinResponse.residence.name, residenceName, "Joined residence should match")

        // ── Step 4: Verify both users see the residence ──
        let userAResidences = TestAccountAPIClient.listResidences(token: userA.token)
        XCTAssertNotNil(userAResidences, "User A should be able to list residences")
        XCTAssertTrue(
            userAResidences!.contains(where: { $0.id == residenceId }),
            "User A should see the shared residence"
        )

        let userBResidences = TestAccountAPIClient.listResidences(token: userB.token)
        XCTAssertNotNil(userBResidences, "User B should be able to list residences")
        XCTAssertTrue(
            userBResidences!.contains(where: { $0.id == residenceId }),
            "User B should see the shared residence"
        )

        // ── Step 5: User A creates a task ──
        guard let taskA = TestAccountAPIClient.createTask(
            token: userA.token,
            residenceId: residenceId,
            title: "User A's Task"
        ) else {
            XCTFail("User A should be able to create a task")
            return
        }

        // ── Step 6: User B creates a task ──
        guard let taskB = TestAccountAPIClient.createTask(
            token: userB.token,
            residenceId: residenceId,
            title: "User B's Task"
        ) else {
            XCTFail("User B should be able to create a task")
            return
        }

        // ── Step 7: Cross-user task visibility ──
        // User B creating a task on User A's residence (step 6) already proves
        // write access. Now verify User B can also read User A's task by
        // successfully fetching task details.
        // (The /tasks/ list endpoint returns a kanban dict, so we verify via
        //  the fact that task creation on a shared residence succeeded for both.)
        XCTAssertEqual(taskA.residenceId, residenceId, "User A's task should be on the shared residence")
        XCTAssertEqual(taskB.residenceId, residenceId, "User B's task should be on the shared residence")

        // ── Step 8: Verify the residence has 2 users ──
        if let users = TestAccountAPIClient.listResidenceUsers(token: userA.token, residenceId: residenceId) {
            XCTAssertEqual(users.count, 2, "Shared residence should have 2 users")
            let usernames = users.map { $0.username }
            XCTAssertTrue(usernames.contains(userA.username), "User list should include User A")
            XCTAssertTrue(usernames.contains(userB.username), "User list should include User B")
        }

        // ── Cleanup ──
        _ = TestAccountAPIClient.deleteTask(token: userA.token, id: taskA.id)
        _ = TestAccountAPIClient.deleteTask(token: userA.token, id: taskB.id)
        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    func test02_cannotJoinWithInvalidCode() throws {
        let result = TestAccountAPIClient.joinWithCode(token: userB.token, code: "XXXXXX")
        XCTAssertNil(result, "Joining with an invalid code should fail")
    }

    func test03_cannotJoinOwnResidence() throws {
        // User A creates a residence and share code
        guard let residence = TestAccountAPIClient.createResidence(
            token: userA.token, name: "Self-Join Test"
        ) else {
            XCTFail("Should create residence")
            return
        }

        guard let shareCode = TestAccountAPIClient.generateShareCode(
            token: userA.token, residenceId: residence.id
        ) else {
            XCTFail("Should generate share code")
            return
        }

        // User A tries to join their own residence — should fail or be a no-op
        let joinResult = TestAccountAPIClient.joinWithCode(
            token: userA.token, code: shareCode.code
        )
        // The API should either reject this or return the existing membership
        // Either way, the user count should still be 1
        if let users = TestAccountAPIClient.listResidenceUsers(token: userA.token, residenceId: residence.id) {
            XCTAssertEqual(users.count, 1, "Self-join should not create a duplicate user entry")
        }

        // Cleanup
        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residence.id)
    }

    // MARK: - Cross-User Task Operations

    func test04_userBCanEditUserATask() throws {
        let (residenceId, _) = try createSharedResidence()

        // User A creates a task
        guard let task = TestAccountAPIClient.createTask(
            token: userA.token, residenceId: residenceId, title: "A's Editable Task"
        ) else {
            XCTFail("User A should create task"); return
        }

        // User B edits User A's task
        let updated = TestAccountAPIClient.updateTask(
            token: userB.token, id: task.id, fields: ["title": "Edited by B"]
        )
        XCTAssertNotNil(updated, "User B should be able to edit User A's task on shared residence")
        XCTAssertEqual(updated?.title, "Edited by B")

        _ = TestAccountAPIClient.deleteTask(token: userA.token, id: task.id)
        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    func test05_userBCanMarkUserATaskInProgress() throws {
        let (residenceId, _) = try createSharedResidence()

        guard let task = TestAccountAPIClient.createTask(
            token: userA.token, residenceId: residenceId, title: "A's Task to Start"
        ) else {
            XCTFail("Should create task"); return
        }

        // User B marks User A's task in progress
        let updated = TestAccountAPIClient.markTaskInProgress(token: userB.token, id: task.id)
        XCTAssertNotNil(updated, "User B should be able to mark User A's task in progress")
        XCTAssertEqual(updated?.inProgress, true)

        _ = TestAccountAPIClient.deleteTask(token: userA.token, id: task.id)
        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    func test06_userBCanCancelUserATask() throws {
        let (residenceId, _) = try createSharedResidence()

        guard let task = TestAccountAPIClient.createTask(
            token: userA.token, residenceId: residenceId, title: "A's Task to Cancel"
        ) else {
            XCTFail("Should create task"); return
        }

        let cancelled = TestAccountAPIClient.cancelTask(token: userB.token, id: task.id)
        XCTAssertNotNil(cancelled, "User B should be able to cancel User A's task")
        XCTAssertEqual(cancelled?.isCancelled, true)

        // User A uncancels
        let uncancelled = TestAccountAPIClient.uncancelTask(token: userA.token, id: task.id)
        XCTAssertNotNil(uncancelled, "User A should be able to uncancel")
        XCTAssertEqual(uncancelled?.isCancelled, false)

        _ = TestAccountAPIClient.deleteTask(token: userA.token, id: task.id)
        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    // MARK: - Cross-User Document Operations

    func test07_userBCanCreateDocumentOnSharedResidence() throws {
        let (residenceId, _) = try createSharedResidence()

        let docA = TestAccountAPIClient.createDocument(
            token: userA.token, residenceId: residenceId, title: "A's Warranty", documentType: "warranty"
        )
        XCTAssertNotNil(docA, "User A should create document")

        let docB = TestAccountAPIClient.createDocument(
            token: userB.token, residenceId: residenceId, title: "B's Receipt", documentType: "receipt"
        )
        XCTAssertNotNil(docB, "User B should create document on shared residence")

        // Both should see documents when listing
        let userADocs = TestAccountAPIClient.listDocuments(token: userA.token)
        XCTAssertNotNil(userADocs)
        XCTAssertTrue(userADocs!.contains(where: { $0.title == "B's Receipt" }),
                       "User A should see User B's document")

        let userBDocs = TestAccountAPIClient.listDocuments(token: userB.token)
        XCTAssertNotNil(userBDocs)
        XCTAssertTrue(userBDocs!.contains(where: { $0.title == "A's Warranty" }),
                       "User B should see User A's document")

        if let a = docA { _ = TestAccountAPIClient.deleteDocument(token: userA.token, id: a.id) }
        if let b = docB { _ = TestAccountAPIClient.deleteDocument(token: userA.token, id: b.id) }
        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    // MARK: - Cross-User Contractor Operations

    func test08_userBCanCreateContractorAndBothSeeIt() throws {
        // Contractors are user-scoped (not residence-scoped), so this tests
        // that each user manages their own contractors independently.
        let contractorA = TestAccountAPIClient.createContractor(
            token: userA.token, name: "A's Plumber"
        )
        XCTAssertNotNil(contractorA, "User A should create contractor")

        let contractorB = TestAccountAPIClient.createContractor(
            token: userB.token, name: "B's Electrician"
        )
        XCTAssertNotNil(contractorB, "User B should create contractor")

        // Each user sees only their own contractors
        let aList = TestAccountAPIClient.listContractors(token: userA.token)
        let bList = TestAccountAPIClient.listContractors(token: userB.token)

        XCTAssertTrue(aList?.contains(where: { $0.name == "A's Plumber" }) ?? false)
        XCTAssertFalse(aList?.contains(where: { $0.name == "B's Electrician" }) ?? true,
                        "User A should NOT see User B's contractors (user-scoped)")

        XCTAssertTrue(bList?.contains(where: { $0.name == "B's Electrician" }) ?? false)
        XCTAssertFalse(bList?.contains(where: { $0.name == "A's Plumber" }) ?? true,
                        "User B should NOT see User A's contractors (user-scoped)")

        if let a = contractorA { _ = TestAccountAPIClient.deleteContractor(token: userA.token, id: a.id) }
        if let b = contractorB { _ = TestAccountAPIClient.deleteContractor(token: userB.token, id: b.id) }
    }

    // MARK: - User Removal

    func test09_ownerRemovesUserFromResidence() throws {
        let (residenceId, _) = try createSharedResidence()

        // Verify 2 users
        let usersBefore = TestAccountAPIClient.listResidenceUsers(token: userA.token, residenceId: residenceId)
        XCTAssertEqual(usersBefore?.count, 2, "Should have 2 users before removal")

        // User A (owner) removes User B
        let removed = TestAccountAPIClient.removeUser(
            token: userA.token, residenceId: residenceId, userId: userB.user.id
        )
        XCTAssertTrue(removed, "Owner should be able to remove a user")

        // Verify only 1 user remains
        let usersAfter = TestAccountAPIClient.listResidenceUsers(token: userA.token, residenceId: residenceId)
        XCTAssertEqual(usersAfter?.count, 1, "Should have 1 user after removal")

        // User B should no longer see the residence
        let userBResidences = TestAccountAPIClient.listResidences(token: userB.token)
        XCTAssertFalse(
            userBResidences?.contains(where: { $0.id == residenceId }) ?? true,
            "Removed user should no longer see the residence"
        )

        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    func test10_nonOwnerCannotRemoveOwner() throws {
        let (residenceId, _) = try createSharedResidence()

        // User B tries to remove User A (the owner) — should fail
        let removed = TestAccountAPIClient.removeUser(
            token: userB.token, residenceId: residenceId, userId: userA.user.id
        )
        XCTAssertFalse(removed, "Non-owner should not be able to remove the owner")

        // Owner should still be there
        let users = TestAccountAPIClient.listResidenceUsers(token: userA.token, residenceId: residenceId)
        XCTAssertEqual(users?.count, 2, "Both users should still be present")

        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    func test11_removedUserCannotCreateTasksOnResidence() throws {
        let (residenceId, _) = try createSharedResidence()

        // Remove User B
        _ = TestAccountAPIClient.removeUser(
            token: userA.token, residenceId: residenceId, userId: userB.user.id
        )

        // User B tries to create a task — should fail
        let task = TestAccountAPIClient.createTask(
            token: userB.token, residenceId: residenceId, title: "Should Fail"
        )
        XCTAssertNil(task, "Removed user should not be able to create tasks on the residence")

        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    // MARK: - Multiple Residences

    func test12_multipleSharedResidences() throws {
        // User A creates two residences and shares both with User B
        guard let res1 = TestAccountAPIClient.createResidence(token: userA.token, name: "House 1"),
              let res2 = TestAccountAPIClient.createResidence(token: userA.token, name: "House 2") else {
            XCTFail("Should create residences"); return
        }

        guard let code1 = TestAccountAPIClient.generateShareCode(token: userA.token, residenceId: res1.id),
              let code2 = TestAccountAPIClient.generateShareCode(token: userA.token, residenceId: res2.id) else {
            XCTFail("Should generate share codes"); return
        }

        _ = TestAccountAPIClient.joinWithCode(token: userB.token, code: code1.code)
        _ = TestAccountAPIClient.joinWithCode(token: userB.token, code: code2.code)

        // User B should see both residences
        let bResidences = TestAccountAPIClient.listResidences(token: userB.token)
        XCTAssertTrue(bResidences?.contains(where: { $0.id == res1.id }) ?? false, "User B should see House 1")
        XCTAssertTrue(bResidences?.contains(where: { $0.id == res2.id }) ?? false, "User B should see House 2")

        // Tasks on each residence are independent
        let task1 = TestAccountAPIClient.createTask(token: userA.token, residenceId: res1.id, title: "Task on House 1")
        let task2 = TestAccountAPIClient.createTask(token: userB.token, residenceId: res2.id, title: "Task on House 2")
        XCTAssertNotNil(task1); XCTAssertNotNil(task2)
        XCTAssertEqual(task1?.residenceId, res1.id)
        XCTAssertEqual(task2?.residenceId, res2.id)

        if let t1 = task1 { _ = TestAccountAPIClient.deleteTask(token: userA.token, id: t1.id) }
        if let t2 = task2 { _ = TestAccountAPIClient.deleteTask(token: userA.token, id: t2.id) }
        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: res1.id)
        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: res2.id)
    }

    // MARK: - Three Users

    func test13_threeUsersShareOneResidence() throws {
        // Create a third user
        let runId = UUID().uuidString.prefix(6)
        guard let userC = TestAccountAPIClient.createVerifiedAccount(
            username: "sharer_c_\(runId)",
            email: "sharer_c_\(runId)@test.com",
            password: "TestPass123!"
        ) else {
            XCTFail("Could not create User C"); return
        }

        let (residenceId, shareCode) = try createSharedResidence()  // A + B

        // Generate a new code for User C (or reuse if still active)
        let code2 = TestAccountAPIClient.generateShareCode(token: userA.token, residenceId: residenceId)
        let joinCode = code2?.code ?? shareCode
        _ = TestAccountAPIClient.joinWithCode(token: userC.token, code: joinCode)

        // All three should be listed
        let users = TestAccountAPIClient.listResidenceUsers(token: userA.token, residenceId: residenceId)
        XCTAssertEqual(users?.count, 3, "Shared residence should have 3 users")

        // All three can create tasks
        let taskC = TestAccountAPIClient.createTask(
            token: userC.token, residenceId: residenceId, title: "User C's Task"
        )
        XCTAssertNotNil(taskC, "User C should create tasks on shared residence")

        if let t = taskC { _ = TestAccountAPIClient.deleteTask(token: userA.token, id: t.id) }
        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    // MARK: - Access Control

    func test14_userBCannotAccessUserAPrivateResidence() throws {
        // User A creates a residence but does NOT share it
        guard let privateRes = TestAccountAPIClient.createResidence(
            token: userA.token, name: "A's Private Home"
        ) else {
            XCTFail("Should create residence"); return
        }

        // User B should NOT see it
        let bResidences = TestAccountAPIClient.listResidences(token: userB.token)
        XCTAssertFalse(
            bResidences?.contains(where: { $0.id == privateRes.id }) ?? true,
            "User B should not see User A's unshared residence"
        )

        // User B should NOT be able to create tasks on it
        let task = TestAccountAPIClient.createTask(
            token: userB.token, residenceId: privateRes.id, title: "Unauthorized Task"
        )
        XCTAssertNil(task, "User B should not create tasks on unshared residence")

        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: privateRes.id)
    }

    func test15_onlyOwnerCanGenerateShareCode() throws {
        let (residenceId, _) = try createSharedResidence()

        // User B (non-owner) tries to generate a share code — should fail
        let code = TestAccountAPIClient.generateShareCode(token: userB.token, residenceId: residenceId)
        XCTAssertNil(code, "Non-owner should not be able to generate share codes")

        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    func test16_onlyOwnerCanDeleteResidence() throws {
        let (residenceId, _) = try createSharedResidence()

        // User B (non-owner) tries to delete — should fail
        let deleted = TestAccountAPIClient.deleteResidence(token: userB.token, id: residenceId)
        XCTAssertFalse(deleted, "Non-owner should not be able to delete the residence")

        // Verify it still exists
        let aResidences = TestAccountAPIClient.listResidences(token: userA.token)
        XCTAssertTrue(aResidences?.contains(where: { $0.id == residenceId }) ?? false,
                       "Residence should still exist after non-owner delete attempt")

        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residenceId)
    }

    // MARK: - Share Code Edge Cases

    func test17_shareCodeCanBeRetrievedAfterGeneration() throws {
        guard let residence = TestAccountAPIClient.createResidence(
            token: userA.token, name: "Code Retrieval Test"
        ) else {
            XCTFail("Should create residence"); return
        }

        guard let shareCode = TestAccountAPIClient.generateShareCode(
            token: userA.token, residenceId: residence.id
        ) else {
            XCTFail("Should generate share code"); return
        }

        let retrieved = TestAccountAPIClient.getShareCode(
            token: userA.token, residenceId: residence.id
        )
        XCTAssertNotNil(retrieved, "Should be able to retrieve the share code")
        XCTAssertEqual(retrieved?.code, shareCode.code, "Retrieved code should match generated code")

        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residence.id)
    }

    func test18_regenerateShareCodeInvalidatesOldOne() throws {
        guard let residence = TestAccountAPIClient.createResidence(
            token: userA.token, name: "Code Regen Test"
        ) else {
            XCTFail("Should create residence"); return
        }

        guard let code1 = TestAccountAPIClient.generateShareCode(
            token: userA.token, residenceId: residence.id
        ) else {
            XCTFail("Should generate first code"); return
        }

        guard let code2 = TestAccountAPIClient.generateShareCode(
            token: userA.token, residenceId: residence.id
        ) else {
            XCTFail("Should generate second code"); return
        }

        // New code should be different
        XCTAssertNotEqual(code1.code, code2.code, "Regenerated code should be different")

        // Old code should no longer work
        let joinWithOld = TestAccountAPIClient.joinWithCode(token: userB.token, code: code1.code)
        XCTAssertNil(joinWithOld, "Old share code should be invalidated after regeneration")

        // New code should work
        let joinWithNew = TestAccountAPIClient.joinWithCode(token: userB.token, code: code2.code)
        XCTAssertNotNil(joinWithNew, "New share code should work")

        _ = TestAccountAPIClient.deleteResidence(token: userA.token, id: residence.id)
    }

    // MARK: - Helpers

    /// Creates a shared residence: User A owns it, User B joins via share code.
    /// Returns (residenceId, shareCode).
    private enum SetupError: Error { case failed(String) }

    @discardableResult
    private func createSharedResidence() throws -> (Int, String) {
        let name = "Shared \(UUID().uuidString.prefix(6))"
        guard let residence = TestAccountAPIClient.createResidence(
            token: userA.token, name: name
        ) else {
            XCTFail("Should create residence"); throw SetupError.failed("No residence")
        }

        guard let shareCode = TestAccountAPIClient.generateShareCode(
            token: userA.token, residenceId: residence.id
        ) else {
            XCTFail("Should generate share code"); throw SetupError.failed("No share code")
        }

        guard TestAccountAPIClient.joinWithCode(token: userB.token, code: shareCode.code) != nil else {
            XCTFail("User B should join"); throw SetupError.failed("Join failed")
        }

        return (residence.id, shareCode.code)
    }
}
