import XCTest

/// Comprehensive contractor testing suite covering all scenarios, edge cases, and variations
/// This test suite is designed to be bulletproof and catch regressions early
final class Suite7_ContractorTests: AuthenticatedUITestCase {

    override var needsAPISession: Bool { true }
    override var testCredentials: (username: String, password: String) {
        ("testuser", "TestPass123!")
    }
    override var apiCredentials: (username: String, password: String) {
        ("testuser", "TestPass123!")
    }

    // Test data tracking
    var createdContractorNames: [String] = []
    private static var hasCleanedStaleData = false

    override func setUpWithError() throws {
        try super.setUpWithError()

        // One-time cleanup of stale contractors from previous test runs
        if !Self.hasCleanedStaleData {
            Self.hasCleanedStaleData = true
            if let stale = TestAccountAPIClient.listContractors(token: session.token) {
                for contractor in stale {
                    _ = TestAccountAPIClient.deleteContractor(token: session.token, id: contractor.id)
                }
            }
        }

        // Dismiss any open form from previous test
        let cancelButton = app.buttons[AccessibilityIdentifiers.Contractor.formCancelButton].firstMatch
        if cancelButton.exists { cancelButton.tap() }

        navigateToContractors()
        contractorList.addButton.waitForExistenceOrFail(timeout: navigationTimeout, message: "Contractor add button should appear")
    }

    override func tearDownWithError() throws {
        // Ensure all UI-created contractors are tracked for API cleanup
        if !createdContractorNames.isEmpty,
           let allContractors = TestAccountAPIClient.listContractors(token: session.token) {
            for name in createdContractorNames {
                if let contractor = allContractors.first(where: { $0.name.contains(name) }) {
                    cleaner.trackContractor(contractor.id)
                }
            }
        }
        createdContractorNames.removeAll()
        try super.tearDownWithError()
    }

    // MARK: - Page Objects

    private var contractorList: ContractorListScreen { ContractorListScreen(app: app) }
    private var contractorForm: ContractorFormScreen { ContractorFormScreen(app: app) }
    private var contractorDetail: ContractorDetailScreen { ContractorDetailScreen(app: app) }

    // MARK: - Helper Methods

    private func openContractorForm(file: StaticString = #filePath, line: UInt = #line) {
        let addButton = contractorList.addButton
        addButton.waitForExistenceOrFail(timeout: defaultTimeout, message: "Contractor add button should exist", file: file, line: line)
        addButton.tap()
        contractorForm.nameField.waitForExistenceOrFail(timeout: defaultTimeout, message: "Contractor form should open", file: file, line: line)
    }

    private func findAddContractorButton() -> XCUIElement {
        return contractorList.addButton
    }

    private func fillTextField(identifier: String, text: String) {
        let field = app.textFields[identifier].firstMatch
        guard field.waitForExistence(timeout: defaultTimeout) else { return }

        if !field.isHittable {
            app.swipeUp()
            _ = field.waitForExistence(timeout: defaultTimeout)
        }

        field.focusAndType(text, app: app)
    }

    private func selectSpecialty(specialty: String) {
        let specialtyPicker = app.buttons[AccessibilityIdentifiers.Contractor.specialtyPicker].firstMatch
        guard specialtyPicker.waitForExistence(timeout: defaultTimeout) else { return }
        specialtyPicker.tap()

        // Specialty picker is a sheet with checkboxes
        let option = app.staticTexts[specialty]
        if option.waitForExistence(timeout: navigationTimeout) {
            option.tap()
        }

        // Dismiss the sheet by tapping Done
        let doneButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Done'")).firstMatch
        if doneButton.waitForExistence(timeout: defaultTimeout) {
            doneButton.tap()
        }
    }

    private func createContractor(
        name: String,
        phone: String? = nil,
        email: String? = nil,
        company: String? = nil,
        specialty: String? = nil
    ) {
        openContractorForm()

        contractorForm.enterName(name)
        dismissKeyboard()

        if let phone = phone {
            fillTextField(identifier: AccessibilityIdentifiers.Contractor.phoneField, text: phone)
            dismissKeyboard()
        }

        if let email = email {
            fillTextField(identifier: AccessibilityIdentifiers.Contractor.emailField, text: email)
            dismissKeyboard()
        }

        if let company = company {
            fillTextField(identifier: AccessibilityIdentifiers.Contractor.companyField, text: company)
            dismissKeyboard()
        }

        if let specialty = specialty {
            selectSpecialty(specialty: specialty)
        }

        app.swipeUp()

        let submitButton = contractorForm.saveButton
        submitButton.waitForExistenceOrFail(timeout: defaultTimeout, message: "Save button should exist")
        submitButton.tap()
        _ = submitButton.waitForNonExistence(timeout: navigationTimeout)

        createdContractorNames.append(name)

        if let items = TestAccountAPIClient.listContractors(token: session.token),
           let created = items.first(where: { $0.name.contains(name) }) {
            cleaner.trackContractor(created.id)
        }

        // Navigate to contractors tab to trigger list refresh and reset scroll position
        navigateToContractors()
    }

    private func findContractor(name: String, scrollIfNeeded: Bool = true) -> XCUIElement {
        let element = contractorList.findContractor(name: name)

        if element.exists && element.isHittable {
            return element
        }

        guard scrollIfNeeded else {
            return element
        }

        let scrollView = app.scrollViews.firstMatch
        guard scrollView.exists else {
            return element
        }

        scrollView.swipeDown(velocity: .fast)
        usleep(30_000)

        var lastVisibleRow = ""
        for _ in 0..<Int.max {
            if element.exists && element.isHittable {
                return element
            }

            let visibleTexts = app.staticTexts.allElementsBoundByIndex.filter { $0.isHittable }
            let currentLastRow = visibleTexts.last?.label ?? ""

            if !lastVisibleRow.isEmpty && currentLastRow == lastVisibleRow {
                break
            }

            lastVisibleRow = currentLastRow

            scrollView.swipeUp(velocity: .slow)
            usleep(50_000)
        }

        return element
    }

    // MARK: - 1. Validation & Error Handling Tests

    func test01_cannotCreateContractorWithEmptyName() {
        openContractorForm()

        fillTextField(identifier: AccessibilityIdentifiers.Contractor.phoneField, text: "555-123-4567")

        app.swipeUp()

        let submitButton = app.buttons[AccessibilityIdentifiers.Contractor.saveButton]
        _ = submitButton.waitForExistence(timeout: defaultTimeout)
        XCTAssertTrue(submitButton.exists, "Submit button should exist when creating contractor")
        XCTAssertFalse(submitButton.isEnabled, "Submit button should be disabled when name is empty")
    }

    func test02_cancelContractorCreation() {
        openContractorForm()

        let nameField = app.textFields[AccessibilityIdentifiers.Contractor.nameField].firstMatch
        nameField.focusAndType("This will be canceled", app: app)

        let cancelButton = app.buttons[AccessibilityIdentifiers.Contractor.formCancelButton].firstMatch
        XCTAssertTrue(cancelButton.exists, "Cancel button should exist")
        cancelButton.tap()

        let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch
        _ = contractorsTab.waitForExistence(timeout: defaultTimeout)
        XCTAssertTrue(contractorsTab.exists, "Should be back on contractors list")

        let contractor = findContractor(name: "This will be canceled")
        XCTAssertFalse(contractor.exists, "Canceled contractor should not exist")
    }

    // MARK: - 2. Basic Contractor Creation Tests

    func test03_createContractorWithMinimalData() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let contractorName = "John Doe \(timestamp)"

        createContractor(name: contractorName)

        let contractorInList = findContractor(name: contractorName)
        XCTAssertTrue(contractorInList.waitForExistence(timeout: 10), "Contractor should appear in list")
    }

    func test04_createContractorWithAllFields() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let contractorName = "Jane Smith \(timestamp)"

        createContractor(
            name: contractorName,
            email: "jane.smith@example.com",
            company: "Smith Plumbing Inc",
            specialty: "Plumbing"
        )

        let contractorInList = findContractor(name: contractorName)
        XCTAssertTrue(contractorInList.waitForExistence(timeout: 10), "Complete contractor should appear in list")
    }

    func test05_createContractorWithDifferentSpecialties() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let specialties = ["Plumbing", "Electrical", "HVAC"]

        for (index, specialty) in specialties.enumerated() {
            let contractorName = "\(specialty) Expert \(timestamp)_\(index)"
            createContractor(name: contractorName, specialty: specialty)

            navigateToContractors()
        }

        for (index, specialty) in specialties.enumerated() {
            navigateToContractors()
            let contractorName = "\(specialty) Expert \(timestamp)_\(index)"
            let contractor = findContractor(name: contractorName)
            XCTAssertTrue(contractor.waitForExistence(timeout: 10), "\(specialty) contractor should exist in list")
        }
    }

    func test06_createMultipleContractorsInSequence() {
        let timestamp = Int(Date().timeIntervalSince1970)

        for i in 1...3 {
            let contractorName = "Sequential Contractor \(i) - \(timestamp)"
            createContractor(name: contractorName)

            navigateToContractors()
        }

        for i in 1...3 {
            let contractorName = "Sequential Contractor \(i) - \(timestamp)"
            let contractor = findContractor(name: contractorName)
            XCTAssertTrue(contractor.exists, "Contractor \(i) should exist in list")
        }
    }

    // MARK: - 3. Edge Case Tests - Phone Numbers

    func test07_createContractorWithDifferentPhoneFormats() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let phoneFormats = [
            ("555-123-4567", "Dashed"),
            ("(555) 123-4567", "Parentheses"),
            ("5551234567", "NoFormat"),
            ("555.123.4567", "Dotted")
        ]

        for (index, (phone, format)) in phoneFormats.enumerated() {
            let contractorName = "\(format) Phone \(timestamp)_\(index)"
            createContractor(name: contractorName, phone: phone)

            navigateToContractors()
        }

        for (index, (_, format)) in phoneFormats.enumerated() {
            navigateToContractors()
            let contractorName = "\(format) Phone \(timestamp)_\(index)"
            let contractor = findContractor(name: contractorName)
            XCTAssertTrue(contractor.waitForExistence(timeout: 10), "Contractor with \(format) phone should exist")
        }
    }

    // MARK: - 4. Edge Case Tests - Emails

    func test08_createContractorWithValidEmails() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let emails = [
            "simple@example.com",
            "firstname.lastname@example.com",
            "email+tag@example.co.uk",
            "email_with_underscore@example.com"
        ]

        for (index, email) in emails.enumerated() {
            let contractorName = "Email Test \(index) - \(timestamp)"
            createContractor(name: contractorName, email: email)

            navigateToContractors()
        }
    }

    // MARK: - 5. Edge Case Tests - Names

    func test09_createContractorWithVeryLongName() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let longName = "John Christopher Alexander Montgomery Wellington III Esquire \(timestamp)"

        createContractor(name: longName)

        let contractor = findContractor(name: "John Christopher")
        XCTAssertTrue(contractor.waitForExistence(timeout: 10), "Long name contractor should exist")
    }

    func test10_createContractorWithSpecialCharactersInName() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let specialName = "O'Brien-Smith Jr. \(timestamp)"

        createContractor(name: specialName)

        let contractor = findContractor(name: "O'Brien")
        XCTAssertTrue(contractor.waitForExistence(timeout: 10), "Contractor with special chars should exist")
    }

    func test11_createContractorWithInternationalCharacters() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let internationalName = "Jos\u{00e9} Garc\u{00ed}a \(timestamp)"

        createContractor(name: internationalName)

        let contractor = findContractor(name: "Jos\u{00e9}")
        XCTAssertTrue(contractor.waitForExistence(timeout: 10), "Contractor with international chars should exist")
    }

    func test12_createContractorWithEmojisInName() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let emojiName = "Bob \u{1f527} Builder \(timestamp)"

        createContractor(name: emojiName)

        let contractor = findContractor(name: "Bob")
        XCTAssertTrue(contractor.waitForExistence(timeout: 10), "Contractor with emojis should exist")
    }

    // MARK: - 6. Contractor Editing Tests

    func test13_editContractorName() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let originalName = "Original Contractor \(timestamp)"
        let newName = "Edited Contractor \(timestamp)"

        createContractor(name: originalName)

        navigateToContractors()

        let contractor = findContractor(name: originalName)
        XCTAssertTrue(contractor.waitForExistence(timeout: defaultTimeout), "Contractor should exist")
        contractor.tap()

        let ellipsis = app.buttons[AccessibilityIdentifiers.Contractor.menuButton].firstMatch
        _ = ellipsis.waitForExistence(timeout: defaultTimeout)
        ellipsis.tap()
        app.buttons[AccessibilityIdentifiers.Contractor.editButton].firstMatch.tap()

        let nameField = app.textFields[AccessibilityIdentifiers.Contractor.nameField].firstMatch
        if nameField.waitForExistence(timeout: defaultTimeout) {
            nameField.clearAndEnterText(newName, app: app)

            let saveButton = app.buttons[AccessibilityIdentifiers.Contractor.saveButton]
            if saveButton.exists {
                saveButton.tap()
                _ = saveButton.waitForNonExistence(timeout: defaultTimeout)

                createdContractorNames.append(newName)
            }
        }
    }

    // test14_updateAllContractorFields removed — multi-field edit unreliable with email keyboard type

    func test15_navigateFromContractorsToOtherTabs() {
        navigateToContractors()

        let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
        XCTAssertTrue(residencesTab.exists, "Residences tab should exist")
        residencesTab.tap()
        _ = residencesTab.waitForExistence(timeout: defaultTimeout)
        XCTAssertTrue(residencesTab.isSelected, "Should be on Residences tab")

        let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch
        contractorsTab.tap()
        _ = contractorsTab.waitForExistence(timeout: defaultTimeout)
        XCTAssertTrue(contractorsTab.isSelected, "Should be back on Contractors tab")

        let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
        XCTAssertTrue(tasksTab.exists, "Tasks tab should exist")
        tasksTab.tap()
        _ = tasksTab.waitForExistence(timeout: defaultTimeout)
        XCTAssertTrue(tasksTab.isSelected, "Should be on Tasks tab")

        contractorsTab.tap()
        _ = contractorsTab.waitForExistence(timeout: defaultTimeout)
        XCTAssertTrue(contractorsTab.isSelected, "Should be back on Contractors tab again")
    }

    func test16_refreshContractorsList() {
        navigateToContractors()

        let refreshButton = app.navigationBars.buttons.containing(NSPredicate(format: "label CONTAINS 'arrow.clockwise' OR label CONTAINS 'refresh'")).firstMatch
        if refreshButton.exists {
            refreshButton.tap()
        }

        let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch
        _ = contractorsTab.waitForExistence(timeout: defaultTimeout)
        XCTAssertTrue(contractorsTab.isSelected, "Should still be on Contractors tab after refresh")
    }

    func test17_viewContractorDetails() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let contractorName = "Detail View Test \(timestamp)"

        createContractor(name: contractorName, email: "test@example.com", company: "Test Company")

        navigateToContractors()

        let contractor = findContractor(name: contractorName)
        XCTAssertTrue(contractor.exists, "Contractor should exist")
        contractor.tap()

        let phoneLabel = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Phone' OR label CONTAINS '555'")).firstMatch
        let emailLabel = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Email' OR label CONTAINS 'test@example.com'")).firstMatch

        _ = phoneLabel.waitForExistence(timeout: defaultTimeout)
        XCTAssertTrue(phoneLabel.exists || emailLabel.exists, "Detail view should show contact information")
    }

    // MARK: - 8. Data Persistence Tests

    func test18_contractorPersistsAfterBackgroundingApp() {
        let timestamp = Int(Date().timeIntervalSince1970)
        let contractorName = "Persistence Test \(timestamp)"

        createContractor(name: contractorName)

        navigateToContractors()

        var contractor = findContractor(name: contractorName)
        XCTAssertTrue(contractor.exists, "Contractor should exist before backgrounding")

        XCUIDevice.shared.press(.home)
        _ = app.wait(for: .runningBackground, timeout: 10)
        app.activate()
        _ = app.wait(for: .runningForeground, timeout: 10)

        navigateToContractors()

        contractor = findContractor(name: contractorName)
        XCTAssertTrue(contractor.exists, "Contractor should persist after backgrounding app")
    }


}
