import Foundation
import Testing
@testable import Forji

struct KeychainManagerTests {

    // MARK: - Key format consistency

    @Test func keychainKeyFormatMatchesBetweenSaveAndGet() async throws {
        let server = "https://forgejo.example.com"
        let username = "testuser"
        let password = "secret123"

        // Save and retrieve to verify key format consistency
        try await KeychainManager.shared.savePassword(password, for: server, username: username)
        let retrieved = try await KeychainManager.shared.getPassword(for: server, username: username)
        #expect(retrieved == password)

        // Clean up
        try await KeychainManager.shared.deletePassword(for: server, username: username)
    }

    @Test func keychainDeleteNonExistentDoesNotThrow() async throws {
        // Deleting a key that doesn't exist should not throw
        // (errSecItemNotFound is accepted)
        try await KeychainManager.shared.deletePassword(
            for: "https://nonexistent.example.com",
            username: "nobody"
        )
    }

    @Test func keychainGetNonExistentThrowsNotFound() async {
        do {
            _ = try await KeychainManager.shared.getPassword(
                for: "https://nonexistent.example.com",
                username: "nobody"
            )
            Issue.record("Expected KeychainError.notFound")
        } catch is KeychainError {
            // Expected
        } catch {
            Issue.record("Unexpected error: \(error)")
        }
    }

    @Test func keychainSaveOverwritesExistingEntry() async throws {
        let server = "https://overwrite-test.example.com"
        let username = "user"

        try await KeychainManager.shared.savePassword("first", for: server, username: username)
        try await KeychainManager.shared.savePassword("second", for: server, username: username)

        let retrieved = try await KeychainManager.shared.getPassword(for: server, username: username)
        #expect(retrieved == "second")

        // Clean up
        try await KeychainManager.shared.deletePassword(for: server, username: username)
    }

    @Test func keychainDifferentUsernamesSameServer() async throws {
        let server = "https://multi-user.example.com"

        try await KeychainManager.shared.savePassword("pass1", for: server, username: "user1")
        try await KeychainManager.shared.savePassword("pass2", for: server, username: "user2")

        let p1 = try await KeychainManager.shared.getPassword(for: server, username: "user1")
        let p2 = try await KeychainManager.shared.getPassword(for: server, username: "user2")
        #expect(p1 == "pass1")
        #expect(p2 == "pass2")

        // Clean up
        try await KeychainManager.shared.deletePassword(for: server, username: "user1")
        try await KeychainManager.shared.deletePassword(for: server, username: "user2")
    }

    @Test func keychainTokenSaveAndRetrieve() async throws {
        let server = "https://token-test.example.com"
        let username = "tokenuser"
        let token = "abc123tokenvalue"

        try await KeychainManager.shared.saveToken(token, for: server, username: username)
        let retrieved = try await KeychainManager.shared.getToken(for: server, username: username)
        #expect(retrieved == token)

        // Clean up
        try await KeychainManager.shared.deleteToken(for: server, username: username)

        // Verify deletion
        do {
            _ = try await KeychainManager.shared.getToken(for: server, username: username)
            Issue.record("Expected KeychainError.notFound after delete")
        } catch is KeychainError {
            // Expected
        }
    }

    @Test func keychainTokenAndPasswordAreSeparateKeys() async throws {
        let server = "https://separate-keys.example.com"
        let username = "user"
        let password = "mypassword"
        let token = "mytoken"

        try await KeychainManager.shared.savePassword(password, for: server, username: username)
        try await KeychainManager.shared.saveToken(token, for: server, username: username)

        let retrievedPassword = try await KeychainManager.shared.getPassword(for: server, username: username)
        let retrievedToken = try await KeychainManager.shared.getToken(for: server, username: username)

        #expect(retrievedPassword == password)
        #expect(retrievedToken == token)

        // Clean up
        try await KeychainManager.shared.deletePassword(for: server, username: username)
        try await KeychainManager.shared.deleteToken(for: server, username: username)
    }

    @Test func keychainNormalizedAndRawURLsAreDifferentKeys() async throws {
        // This tests the exact issue: if you save with normalized URL but delete with raw URL,
        // the deletion will miss.
        let raw = "https://forgejo.example.com/"
        let normalized = "https://forgejo.example.com"

        try await KeychainManager.shared.savePassword("pass", for: normalized, username: "user")

        // Getting with the raw URL (with trailing slash) should fail because keys differ
        do {
            _ = try await KeychainManager.shared.getPassword(for: raw, username: "user")
            Issue.record("Should not find password with non-normalized key")
        } catch is KeychainError {
            // Expected, keys differ
        }

        // Clean up
        try await KeychainManager.shared.deletePassword(for: normalized, username: "user")
    }

    @Test func keychainDeleteCredentialsRemovesBothPasswordAndToken() async throws {
        let server = "https://delete-credentials.example.com"
        let username = "user"

        try await KeychainManager.shared.savePassword("mypassword", for: server, username: username)
        try await KeychainManager.shared.saveToken("mytoken", for: server, username: username)

        try await KeychainManager.shared.deleteCredentials(for: server, username: username)

        do {
            _ = try await KeychainManager.shared.getPassword(for: server, username: username)
            Issue.record("Expected KeychainError.notFound for password after deleteCredentials")
        } catch is KeychainError {
            // Expected
        } catch {
            Issue.record("Unexpected error: \(error)")
        }

        do {
            _ = try await KeychainManager.shared.getToken(for: server, username: username)
            Issue.record("Expected KeychainError.notFound for token after deleteCredentials")
        } catch is KeychainError {
            // Expected, this is the assertion that fails under the old leak
        } catch {
            Issue.record("Unexpected error: \(error)")
        }
    }

    @Test func keychainDeleteCredentialsNonExistentDoesNotThrow() async throws {
        // Deleting credentials for an account with nothing stored must not throw.
        try await KeychainManager.shared.deleteCredentials(
            for: "https://nonexistent-credentials.example.com",
            username: "nobody"
        )
    }
}
