import XCTest

final class AscentlyTests: XCTestCase {

    override func setUpWithError() throws {
    }

    override func tearDownWithError() throws {
    }

    // MARK: - Data Validation Tests

    func testDifficultyGradeComparison() throws {
        // Test basic difficulty grade string comparison
        let grade1 = "V5"
        let grade2 = "V3"
        let grade3 = "V5"

        XCTAssertEqual(grade1, grade3)
        XCTAssertNotEqual(grade1, grade2)
        XCTAssertFalse(grade1.isEmpty)
    }

    func testClimbTypeValidation() throws {
        // Test climb type validation
        let validClimbTypes = ["ROPE", "BOULDER"]

        for climbType in validClimbTypes {
            XCTAssertTrue(validClimbTypes.contains(climbType))
            XCTAssertFalse(climbType.isEmpty)
        }

        let invalidTypes = ["", "unknown", "invalid", "sport", "trad", "toprope"]
        for invalidType in invalidTypes {
            if !invalidType.isEmpty {
                XCTAssertFalse(validClimbTypes.contains(invalidType))
            }
        }
    }

    func testDateFormatting() throws {
        // Test ISO 8601 date formatting
        let formatter = ISO8601DateFormatter()
        let date = Date()
        let formattedDate = formatter.string(from: date)

        XCTAssertFalse(formattedDate.isEmpty)
        XCTAssertTrue(formattedDate.contains("T"))
        XCTAssertTrue(formattedDate.hasSuffix("Z"))

        // Test parsing back
        let parsedDate = formatter.date(from: formattedDate)
        XCTAssertNotNil(parsedDate)
    }

    func testSessionDurationCalculation() throws {
        // Test session duration calculation
        let startTime = Date()
        let endTime = Date(timeInterval: 3600, since: startTime)  // 1 hour later
        let duration = endTime.timeIntervalSince(startTime)

        XCTAssertEqual(duration, 3600, accuracy: 1.0)
        XCTAssertGreaterThan(duration, 0)
    }

    func testAttemptResultValidation() throws {
        // Test attempt result validation
        let validResults = ["completed", "failed", "flash", "project"]

        for result in validResults {
            XCTAssertTrue(validResults.contains(result))
            XCTAssertFalse(result.isEmpty)
        }
    }

    func testGymCreation() throws {
        // Test gym model creation with basic validation
        let gymName = "Test Climbing Gym"
        let location = "Test City"
        let supportedTypes = ["BOULDER", "ROPE"]

        XCTAssertFalse(gymName.isEmpty)
        XCTAssertFalse(location.isEmpty)
        XCTAssertFalse(supportedTypes.isEmpty)
        XCTAssertEqual(supportedTypes.count, 2)
        XCTAssertTrue(supportedTypes.contains("BOULDER"))
        XCTAssertTrue(supportedTypes.contains("ROPE"))
    }

    func testProblemValidation() throws {
        // Test problem model validation
        let problemName = "Test Problem"
        let climbType = "BOULDER"
        let difficulty = "V5"
        let tags = ["overhang", "crimpy"]

        XCTAssertFalse(problemName.isEmpty)
        XCTAssertTrue(["BOULDER", "ROPE"].contains(climbType))
        XCTAssertFalse(difficulty.isEmpty)
        XCTAssertEqual(tags.count, 2)
        XCTAssertTrue(tags.allSatisfy { !$0.isEmpty })
    }

    func testSessionStatusTransitions() throws {
        // Test session status transitions
        let validStatuses = ["planned", "active", "completed", "cancelled"]

        for status in validStatuses {
            XCTAssertTrue(validStatuses.contains(status))
            XCTAssertFalse(status.isEmpty)
        }

        // Test status transitions logic
        let initialStatus = "planned"
        let activeStatus = "active"
        let completedStatus = "completed"

        XCTAssertNotEqual(initialStatus, activeStatus)
        XCTAssertNotEqual(activeStatus, completedStatus)
    }

    func testUniqueIDGeneration() throws {
        // Test unique ID generation using UUID
        let id1 = UUID().uuidString
        let id2 = UUID().uuidString

        XCTAssertNotEqual(id1, id2)
        XCTAssertFalse(id1.isEmpty)
        XCTAssertFalse(id2.isEmpty)
        XCTAssertEqual(id1.count, 36)  // UUID string length
        XCTAssertTrue(id1.contains("-"))
    }

    func testDataValidation() throws {
        // Test basic data validation patterns
        let emptyString = ""
        let validString = "test"
        let negativeNumber = -1
        let positiveNumber = 5
        let zeroNumber = 0

        XCTAssertTrue(emptyString.isEmpty)
        XCTAssertFalse(validString.isEmpty)
        XCTAssertLessThan(negativeNumber, 0)
        XCTAssertGreaterThan(positiveNumber, 0)
        XCTAssertEqual(zeroNumber, 0)
    }

    // MARK: - Collection Tests

    func testArrayOperations() throws {
        // Test array operations for climb data
        var problems: [String] = []

        XCTAssertTrue(problems.isEmpty)
        XCTAssertEqual(problems.count, 0)

        problems.append("Problem 1")
        problems.append("Problem 2")

        XCTAssertFalse(problems.isEmpty)
        XCTAssertEqual(problems.count, 2)
        XCTAssertTrue(problems.contains("Problem 1"))

        let filteredProblems = problems.filter { $0.contains("1") }
        XCTAssertEqual(filteredProblems.count, 1)
    }

    func testDictionaryOperations() throws {
        // Test dictionary operations for data storage
        var gymData: [String: Any] = [:]

        XCTAssertTrue(gymData.isEmpty)

        gymData["name"] = "Test Gym"
        gymData["location"] = "Test City"
        gymData["types"] = ["BOULDER", "ROPE"]

        XCTAssertFalse(gymData.isEmpty)
        XCTAssertEqual(gymData.count, 3)
        XCTAssertNotNil(gymData["name"])

        if let name = gymData["name"] as? String {
            XCTAssertEqual(name, "Test Gym")
        } else {
            XCTFail("Failed to cast gym name to String")
        }
    }

    // MARK: - String and Numeric Tests

    func testStringManipulation() throws {
        // Test string operations common in climb data
        let problemName = "  Test Problem V5  "
        let trimmedName = problemName.trimmingCharacters(in: .whitespacesAndNewlines)
        let uppercaseName = trimmedName.uppercased()
        let lowercaseName = trimmedName.lowercased()

        XCTAssertEqual(trimmedName, "Test Problem V5")
        XCTAssertEqual(uppercaseName, "TEST PROBLEM V5")
        XCTAssertEqual(lowercaseName, "test problem v5")

        let components = trimmedName.components(separatedBy: " ")
        XCTAssertEqual(components.count, 3)
        XCTAssertEqual(components.last, "V5")
    }

    func testNumericOperations() throws {
        // Test numeric operations for climb ratings and statistics
        let grades = [3, 5, 7, 4, 6]
        let sum = grades.reduce(0, +)
        let average = Double(sum) / Double(grades.count)
        let maxGrade = grades.max() ?? 0
        let minGrade = grades.min() ?? 0

        XCTAssertEqual(sum, 25)
        XCTAssertEqual(average, 5.0, accuracy: 0.01)
        XCTAssertEqual(maxGrade, 7)
        XCTAssertEqual(minGrade, 3)
    }

    // MARK: - JSON and Data Format Tests

    func testJSONSerialization() throws {
        // Test JSON serialization for basic data structures
        let testData: [String: Any] = [
            "id": "test123",
            "name": "Test Gym",
            "active": true,
            "rating": 4.5,
            "types": ["BOULDER", "ROPE"],
        ]

        XCTAssertNoThrow({
            let jsonData = try JSONSerialization.data(withJSONObject: testData)
            XCTAssertFalse(jsonData.isEmpty)

            let deserializedData =
                try JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
            XCTAssertNotNil(deserializedData)
            XCTAssertEqual(deserializedData?["name"] as? String, "Test Gym")
        })
    }

    func testDateSerialization() throws {
        // Test date serialization for API compatibility
        let date = Date()
        let formatter = ISO8601DateFormatter()
        let dateString = formatter.string(from: date)
        let parsedDate = formatter.date(from: dateString)

        XCTAssertNotNil(parsedDate)
        XCTAssertEqual(date.timeIntervalSince1970, parsedDate!.timeIntervalSince1970, accuracy: 1.0)
    }

    // MARK: - Active Session Preservation Tests

    func testActiveSessionPreservationDuringImport() throws {
        let activeSessionId = UUID()
        let gymId = UUID()

        // Test data structure representing local active session
        let localActiveSession: [String: Any] = [
            "id": activeSessionId.uuidString,
            "gymId": gymId.uuidString,
            "status": "active",
            "date": "2024-01-01",
            "startTime": "2024-01-01T10:00:00Z",
        ]

        // Test data structure representing server sessions (without the active one)
        let serverSessions: [[String: Any]] = [
            [
                "id": UUID().uuidString,
                "gymId": gymId.uuidString,
                "status": "completed",
                "date": "2023-12-31",
                "startTime": "2023-12-31T15:00:00Z",
                "endTime": "2023-12-31T17:00:00Z",
            ]
        ]

        // Verify test setup
        XCTAssertEqual(localActiveSession["status"] as? String, "active")
        XCTAssertEqual(serverSessions.count, 1)
        XCTAssertEqual(serverSessions[0]["status"] as? String, "completed")

        // Verify that the active session ID is not in the server sessions
        let serverSessionIds = serverSessions.compactMap { $0["id"] as? String }
        XCTAssertFalse(serverSessionIds.contains(activeSessionId.uuidString))

        // Test that we can identify an active session
        if let status = localActiveSession["status"] as? String {
            XCTAssertTrue(status == "active")
        } else {
            XCTFail("Failed to extract session status")
        }

        // Test session ID validation
        if let sessionIdString = localActiveSession["id"] as? String,
            let sessionId = UUID(uuidString: sessionIdString)
        {
            XCTAssertEqual(sessionId, activeSessionId)
        } else {
            XCTFail("Failed to parse session ID")
        }

        // Test that combining sessions preserves both local active and server completed
        var combinedSessions = serverSessions
        combinedSessions.append(localActiveSession)

        XCTAssertEqual(combinedSessions.count, 2)

        // Verify both session types are present
        let hasActiveSession = combinedSessions.contains { session in
            (session["status"] as? String) == "active"
        }
        let hasCompletedSession = combinedSessions.contains { session in
            (session["status"] as? String) == "completed"
        }

        XCTAssertTrue(hasActiveSession, "Combined sessions should contain active session")
        XCTAssertTrue(hasCompletedSession, "Combined sessions should contain completed session")
    }

    // MARK: - Orphaned Data Cleanup Tests

    func testOrphanedAttemptDetection() throws {
        // Test that we can detect orphaned attempts (attempts referencing non-existent sessions)

        let validSessionId = UUID()
        let deletedSessionId = UUID()
        let validProblemId = UUID()

        // Simulate a list of valid sessions
        let validSessions = [validSessionId]

        // Simulate attempts - one valid, one orphaned
        let validAttempt: [String: Any] = [
            "id": UUID().uuidString,
            "sessionId": validSessionId.uuidString,
            "problemId": validProblemId.uuidString,
            "result": "completed",
        ]

        let orphanedAttempt: [String: Any] = [
            "id": UUID().uuidString,
            "sessionId": deletedSessionId.uuidString,
            "problemId": validProblemId.uuidString,
            "result": "completed",
        ]

        let allAttempts = [validAttempt, orphanedAttempt]

        // Filter to find orphaned attempts
        let orphaned = allAttempts.filter { attempt in
            guard let sessionIdString = attempt["sessionId"] as? String,
                let sessionId = UUID(uuidString: sessionIdString)
            else {
                return false
            }
            return !validSessions.contains(sessionId)
        }

        XCTAssertEqual(orphaned.count, 1, "Should detect exactly one orphaned attempt")
        XCTAssertEqual(orphaned[0]["sessionId"] as? String, deletedSessionId.uuidString)
    }

    func testOrphanedAttemptRemoval() throws {
        // Test that orphaned attempts can be properly removed from a list

        let validSessionId = UUID()
        let deletedSessionId = UUID()
        let problemId = UUID()

        let validSessions = Set([validSessionId])

        // Create test attempts
        var attempts: [[String: Any]] = [
            [
                "id": UUID().uuidString,
                "sessionId": validSessionId.uuidString,
                "problemId": problemId.uuidString,
                "result": "completed",
            ],
            [
                "id": UUID().uuidString,
                "sessionId": deletedSessionId.uuidString,
                "problemId": problemId.uuidString,
                "result": "failed",
            ],
            [
                "id": UUID().uuidString,
                "sessionId": validSessionId.uuidString,
                "problemId": problemId.uuidString,
                "result": "flash",
            ],
        ]

        XCTAssertEqual(attempts.count, 3, "Should start with 3 attempts")

        // Remove orphaned attempts
        attempts.removeAll { attempt in
            guard let sessionIdString = attempt["sessionId"] as? String,
                let sessionId = UUID(uuidString: sessionIdString)
            else {
                return true
            }
            return !validSessions.contains(sessionId)
        }

        XCTAssertEqual(attempts.count, 2, "Should have 2 attempts after cleanup")

        // Verify remaining attempts are all valid
        for attempt in attempts {
            if let sessionIdString = attempt["sessionId"] as? String,
                let sessionId = UUID(uuidString: sessionIdString)
            {
                XCTAssertTrue(
                    validSessions.contains(sessionId),
                    "All remaining attempts should reference valid sessions")
            }
        }
    }

    func testCascadeDeleteSessionWithAttempts() throws {
        // Test that deleting a session properly tracks all its attempts as deleted

        let sessionId = UUID()
        let problemId = UUID()

        // Create attempts for this session
        let sessionAttempts: [[String: Any]] = [
            [
                "id": UUID().uuidString, "sessionId": sessionId.uuidString,
                "problemId": problemId.uuidString,
            ],
            [
                "id": UUID().uuidString, "sessionId": sessionId.uuidString,
                "problemId": problemId.uuidString,
            ],
            [
                "id": UUID().uuidString, "sessionId": sessionId.uuidString,
                "problemId": problemId.uuidString,
            ],
        ]

        XCTAssertEqual(sessionAttempts.count, 3, "Session should have 3 attempts")

        // Simulate tracking deletions
        var deletedItems: [String] = []

        // Add session to deleted items
        deletedItems.append(sessionId.uuidString)

        // Add all attempts to deleted items
        for attempt in sessionAttempts {
            if let attemptId = attempt["id"] as? String {
                deletedItems.append(attemptId)
            }
        }

        XCTAssertEqual(deletedItems.count, 4, "Should track 1 session + 3 attempts as deleted")
        XCTAssertTrue(deletedItems.contains(sessionId.uuidString), "Should track session deletion")

        // Verify all attempt IDs are tracked
        let attemptIds = sessionAttempts.compactMap { $0["id"] as? String }
        for attemptId in attemptIds {
            XCTAssertTrue(
                deletedItems.contains(attemptId), "Should track attempt \(attemptId) deletion")
        }
    }

    func testDataIntegrityValidation() throws {
        // Test data integrity validation logic

        let gymId = UUID()
        let sessionId = UUID()
        let problemId = UUID()

        // Valid data setup
        let gyms = [gymId]
        let sessions = [(id: sessionId, gymId: gymId)]
        let problems = [(id: problemId, gymId: gymId)]
        let attempts = [
            (id: UUID(), sessionId: sessionId, problemId: problemId),
            (id: UUID(), sessionId: sessionId, problemId: problemId),
        ]

        // Validate that all relationships are correct
        let validGyms = Set(gyms)
        let validSessions = Set(sessions.map { $0.id })
        let validProblems = Set(problems.map { $0.id })

        // Check sessions reference valid gyms
        for session in sessions {
            XCTAssertTrue(validGyms.contains(session.gymId), "Session should reference valid gym")
        }

        // Check problems reference valid gyms
        for problem in problems {
            XCTAssertTrue(validGyms.contains(problem.gymId), "Problem should reference valid gym")
        }

        // Check attempts reference valid sessions and problems
        for attempt in attempts {
            XCTAssertTrue(
                validSessions.contains(attempt.sessionId), "Attempt should reference valid session")
            XCTAssertTrue(
                validProblems.contains(attempt.problemId), "Attempt should reference valid problem")
        }

        // Test integrity check passes
        let hasOrphanedSessions = sessions.contains { !validGyms.contains($0.gymId) }
        let hasOrphanedProblems = problems.contains { !validGyms.contains($0.gymId) }
        let hasOrphanedAttempts = attempts.contains {
            !validSessions.contains($0.sessionId) || !validProblems.contains($0.problemId)
        }

        XCTAssertFalse(hasOrphanedSessions, "Should not have orphaned sessions")
        XCTAssertFalse(hasOrphanedProblems, "Should not have orphaned problems")
        XCTAssertFalse(hasOrphanedAttempts, "Should not have orphaned attempts")
    }
}
