@testable import FeatureInAppNotifications
import Foundation
@testable import StatsigClient
import Testing

@Suite("StatsigClientDynamicConfig Tests")
struct StatsigClientDynamicConfigTests {
    @Test func testGetDynamicConfigNotificationsBanneriOSSuccess() {
        testGetDynamicConfig(
            configData: notificationsBannerMockConfigData,
            expectedResult: NotificationsBannerConfig(
                id: "video_cover_hooks_disclaimer",
                tag: .init(text: "New Feature!"),
                titleText: .init(text: "Video cover art 🤝 Hooks"),
                subtitleText: .init(text: "Published songs with video cover art are now shown in the Hooks feed. You can edit visibility settings in song actions.")
            ),
            configName: .notificationsBanner,
            as: NotificationsBannerConfig.self
        )
    }

    @Test func testGetDynamicConfigNotificationsBannerWithOnlyIOSField() {
        let notificationsBannerIOSOnlyConfig = [
            "ios": [
                "id": "ios_only_notifications_banner",
                "tag": ["text": "iOS Only Banner"],
                "title_text": ["text": "iOS Specific Notifications Title"],
                "subtitle_text": ["text": "This notifications banner config only has iOS data"],
            ],
        ]

        testGetDynamicConfig(
            configData: notificationsBannerIOSOnlyConfig,
            expectedResult: NotificationsBannerConfig(
                id: "ios_only_notifications_banner",
                tag: .init(text: "iOS Only Banner"),
                titleText: .init(text: "iOS Specific Notifications Title"),
                subtitleText: .init(text: "This notifications banner config only has iOS data")
            ),
            configName: .notificationsBanner,
            as: NotificationsBannerConfig.self
        )
    }

    @Test func testGetDynamicConfigNotificationsBannerReturnsNilWhenNoConfig() {
        // Test empty config
        testGetDynamicConfigReturnsNil(configData: [:])

        // Test missing iOS section
        let configWithoutIOS = ["web": notificationsBannerMockConfigData["web"]!, "android": notificationsBannerMockConfigData["android"]!]
        testGetDynamicConfigReturnsNil(configData: configWithoutIOS)

        // Test invalid structure
        let invalidConfig = ["ios": ["invalid": "data"]]
        testGetDynamicConfigReturnsNil(configData: invalidConfig)
    }

    @Test func testNotificationsBannerPlatformKeyExtraction() {
        let client = makeMockStatsigClient(configData: notificationsBannerMockConfigData)

        // Test that it correctly extracts "ios" platform
        let result = client.getDynamicConfig(
            .notificationsBanner,
            as: NotificationsBannerConfig.self
        )

        #expect(result != nil)
    }

    @Test func testNotificationsBannerMissingPlatformKey() {
        let configWithoutIOS = ["web": notificationsBannerMockConfigData["web"]!, "android": notificationsBannerMockConfigData["android"]!]
        let client = makeMockStatsigClient(configData: configWithoutIOS)

        let result = client.getDynamicConfig(
            .notificationsBanner,
            as: NotificationsBannerConfig.self
        )

        #expect(result == nil)
    }

    @Test func testGenericJSONDecoding() {
        let genericConfig = [
            "ios": [
                "id": "test_id",
                "name": "Test Config",
                "enabled": true,
            ],
        ]

        struct TestConfig: Codable, Equatable {
            let id: String
            let name: String
            let enabled: Bool
        }

        let client = makeMockStatsigClient(configData: genericConfig)
        let result = client.getDynamicConfig(
            .notificationsBanner, // Using existing enum
            as: TestConfig.self
        )

        #expect(result != nil)
        #expect(result?.id == "test_id")
        #expect(result?.name == "Test Config")
        #expect(result?.enabled == true)
    }

    @Test func testSnakeCaseConversion() {
        let snakeCaseConfig = [
            "ios": [
                "user_id": "123",
                "display_name": "Test User",
                "is_active": true,
            ],
        ]

        struct TestConfig: Codable, Equatable {
            let userId: String
            let displayName: String
            let isActive: Bool
        }

        let client = makeMockStatsigClient(configData: snakeCaseConfig)
        let result = client.getDynamicConfig(
            .notificationsBanner,
            as: TestConfig.self
        )

        #expect(result != nil)
        #expect(result?.userId == "123")
        #expect(result?.displayName == "Test User")
        #expect(result?.isActive == true)
    }

    @Test func testDynamicConfigEnumRawValues() {
        #expect(DynamicConfig.notificationsBanner.rawValue == "notifications-banner-config")
    }

    @Test func testSnakeCaseDecoding() {
        let json = """
        {
            "id": "test_id",
            "tag": { "text": "Test Tag" },
            "title_text": { "text": "Test Title" },
            "subtitle_text": { "text": "Test Subtitle" }
        }
        """

        let data = json.data(using: .utf8)!
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase

        #expect(throws: Never.self) {
            let config = try decoder.decode(NotificationsBannerConfig.self, from: data)
            #expect(config.id == "test_id")
            #expect(config.titleText.text == "Test Title")
            #expect(config.subtitleText.text == "Test Subtitle")
        }
    }

    // MARK: - Test Helpers

    private func makeMockStatsigClient(configData: [String: Any]? = nil) -> StatsigClient {
        let data = configData ?? notificationsBannerMockConfigData
        return StatsigClient(
            startStatsig: { _ in },
            setUser: { _ in },
            getStableID: { nil },
            trackEvent: { _ in },
            state: { .never },
            currentState: { .initialized },
            getValue: { _, _, _ in nil },
            markExposure: { _, _, _ in },
            getDynamicConfig: { configName, _ in
                #expect(configName == "notifications-banner-config")
                return data
            },
            clearCachedUser: { }
        )
    }

    private func testGetDynamicConfig<T: Codable & Equatable>(
        configData: [String: Any],
        expectedResult: T,
        configName: DynamicConfig,
        as type: T.Type
    ) {
        let client = makeMockStatsigClient(configData: configData)
        let result = client.getDynamicConfig(
            configName,
            as: type
        )

        #expect(result != nil)
        #expect(result == expectedResult)
    }

    private func testGetDynamicConfigReturnsNil(configData: [String: Any]) {
        let client = makeMockStatsigClient(configData: configData)
        let result = client.getDynamicConfig(
            .notificationsBanner,
            as: NotificationsBannerConfig.self
        )

        #expect(result == nil)
    }

    private var notificationsBannerMockConfigData: [String: Any] {
        guard let data = notificationsBannerMockConfigJSON.data(using: .utf8),
              let jsonObject = try? JSONSerialization.jsonObject(with: data),
              let result = jsonObject as? [String: Any]
        else {
            return [:]
        }
        return result
    }

    // MARK: Mock Data

    private let notificationsBannerMockConfigJSON = """
    {
      "web": {
        "type": "web",
        "id": "studio_promo_banner",
        "tag": {
          "text": {
            "text": "New Feature!",
            "key": "banner.tag.new_feature",
            "color": "#000000"
          },
          "color": "#FD429C"
        },
        "title_text": { "text": "Video cover art 🤝 Hooks", "color": "#FFFFFF" },
        "subtitle_text": {
          "text": "Published songs with video cover art are now shown in the Hooks feed. You can edit visibility settings in song actions.",
          "color": "#A3A3A3"
        }
      },
      "ios": {
        "id": "video_cover_hooks_disclaimer",
        "tag": {
          "text": "New Feature!"
        },
        "title_text": { "text": "Video cover art 🤝 Hooks" },
        "subtitle_text": {
          "text": "Published songs with video cover art are now shown in the Hooks feed. You can edit visibility settings in song actions."
        }
      },
      "android": {
        "id": "video_cover_hooks_disclaimer",
        "tag": {
          "text": "New Feature!"
        },
        "title_text": { "text": "Video cover art 🤝 Hooks" },
        "subtitle_text": {
          "text": "Published songs with video cover art are now shown in the Hooks feed. You can edit visibility settings in song actions."
        }
      }
    }
    """
}
