import ComposableArchitecture
import Foundation
import Utilities

// MARK: - App Storage Variable Discovery

public struct AppStorageVariable: Identifiable, Equatable {
    public let id: String
    public let name: String
    public let keyName: String
    public let description: String?
    public var currentValue: AppStorageVariable.AppStorageValue
    public let inferredType: AppStorageType

    public enum AppStorageType: String, CaseIterable {
        case boolean = "Bool"
        case integer = "Int"
        case double = "Double"
        case string = "String"
        case stringArray = "[String]"
        case date = "Date?"
        case timeInterval = "TimeInterval"
        case enumType = "Enum"
        case unknown = "Unknown"
    }

    public enum AppStorageValue: Equatable {
        case boolean(Bool)
        case integer(Int)
        case double(Double)
        case string(String)
        case stringArray([String])
        case date(Date?)
        case timeInterval(TimeInterval)
        case enumType(selectedCase: String, allCases: [String], enumTypeName: String)
        case unknown(String)

        public var displayValue: String {
            switch self {
            case .boolean(let value): return value ? "true" : "false"

            case .integer(let value): return "\(value)"

            case .double(let value): return "\(value)"

            case .string(let value): return value.isEmpty ? "(empty)" : value

            case .stringArray(let value): return "[\(value.count) items]"

            case .date(let value):
                if let date = value {
                    return date.formatted(date: .abbreviated, time: .shortened)
                } else {
                    return "nil"
                }

            case .timeInterval(let value): return "\(value)"

            case .enumType(let selectedCase, _, let enumTypeName): return "\(enumTypeName).\(selectedCase)"

            case .unknown(let value): return value
            }
        }
    }
}

// MARK: - App Storage Discovery Service

public class AppStorageDiscovery {
    public static let shared = AppStorageDiscovery()

    private init() {}

    public func discoverAppStorageVariables() -> [AppStorageVariable] {
        return String.appStorageVariables.compactMap { definition in
            createVariable(from: definition)
        }
    }

    private func createVariable(from definition: AppStorageVariableDefinition) -> AppStorageVariable? {
        let currentValue = readCurrentValue(for: definition)
        let inferredType = mapToAppStorageType(definition.type)

        return AppStorageVariable(
            id: definition.key,
            name: definition.computedDisplayName,
            keyName: definition.key,
            description: definition.description,
            currentValue: currentValue,
            inferredType: inferredType
        )
    }

    private func readCurrentValue(for definition: AppStorageVariableDefinition) -> AppStorageVariable.AppStorageValue {
        let userDefaults = UserDefaults.standard
        let key = definition.key

        switch definition.type {
        case .boolean(let defaultValue):
            let value = userDefaults.object(forKey: key) as? Bool ?? defaultValue
            return .boolean(value)

        case .integer(let defaultValue):
            let value = userDefaults.object(forKey: key) as? Int ?? defaultValue
            return .integer(value)

        case .double(let defaultValue):
            let value = userDefaults.object(forKey: key) as? Double ?? defaultValue
            return .double(value)

        case .string(let defaultValue):
            let value = userDefaults.string(forKey: key) ?? defaultValue
            return .string(value)

        case .stringArray(let defaultValue):
            let value = userDefaults.array(forKey: key) as? [String] ?? defaultValue
            return .stringArray(value)

        case .date(let defaultValue):
            if let data = userDefaults.data(forKey: key),
               let date = try? JSONDecoder().decode(Date?.self, from: data)
            {
                return .date(date)
            }
            return .date(defaultValue)

        case .timeInterval(let defaultValue):
            let value = userDefaults.object(forKey: key) as? TimeInterval ?? defaultValue
            return .timeInterval(value)

        case .enumType(let cases, let defaultCase, let typeName):
            let rawValue = userDefaults.string(forKey: key) ?? defaultCase
            return .enumType(
                selectedCase: rawValue,
                allCases: cases,
                enumTypeName: typeName
            )
        }
    }

    private func mapToAppStorageType(_ variableType: AppStorageVariableType) -> AppStorageVariable.AppStorageType {
        switch variableType {
        case .boolean: return .boolean
        case .integer: return .integer
        case .double: return .double
        case .string: return .string
        case .stringArray: return .stringArray
        case .date: return .date
        case .timeInterval: return .timeInterval
        case .enumType: return .enumType
        }
    }

    public func writeValue(_ value: AppStorageVariable.AppStorageValue, to key: String) {
        let userDefaults = UserDefaults.standard

        switch value {
        case .boolean(let boolValue):
            userDefaults.set(boolValue, forKey: key)

        case .integer(let intValue):
            userDefaults.set(intValue, forKey: key)

        case .double(let doubleValue):
            userDefaults.set(doubleValue, forKey: key)

        case .string(let stringValue):
            userDefaults.set(stringValue, forKey: key)

        case .stringArray(let arrayValue):
            userDefaults.set(arrayValue, forKey: key)

        case .date(let dateValue):
            if let date = dateValue,
               let data = try? JSONEncoder().encode(date)
            {
                userDefaults.set(data, forKey: key)
            } else {
                userDefaults.removeObject(forKey: key)
            }

        case .timeInterval(let timeIntervalValue):
            userDefaults.set(timeIntervalValue, forKey: key)

        case .enumType(let selectedCase, _, _):
            userDefaults.set(selectedCase, forKey: key)

        case .unknown:
            // Don't write unknown values
            break
        }
    }
}

// MARK: - TCA Reducer

@Reducer
public struct AppStorageMenuReducer {
    @ObservableState
    public struct State: Equatable {
        var discoveredVariables: [AppStorageVariable] = []
        var selectedSection: SectionType = .all
        var isLoading = true
        var searchText = ""

        var filteredVariables: [AppStorageVariable] {
            let sectionFiltered = selectedSection == .all ? discoveredVariables :
                discoveredVariables.filter { variable in
                    switch selectedSection {
                    case .booleans: return variable.inferredType == .boolean
                    case .numbers: return [.integer, .double, .timeInterval].contains(variable.inferredType)
                    case .strings: return variable.inferredType == .string
                    case .arrays: return variable.inferredType == .stringArray
                    case .dates: return variable.inferredType == .date
                    case .enums: return variable.inferredType == .enumType
                    case .all: return true
                    }
                }

            if searchText.isEmpty {
                return sectionFiltered
            } else {
                return sectionFiltered.filter { variable in
                    variable.name.localizedCaseInsensitiveContains(searchText) ||
                        variable.keyName.localizedCaseInsensitiveContains(searchText) ||
                        variable.description?.localizedCaseInsensitiveContains(searchText) == true
                }
            }
        }

        public init() {}
    }

    public enum SectionType: String, CaseIterable, Identifiable {
        case all = "All"
        case booleans = "Booleans"
        case numbers = "Numbers"
        case strings = "Strings"
        case arrays = "Arrays"
        case dates = "Dates"
        case enums = "Enums"

        public var id: String { rawValue }

        public var displayName: String {
            switch self {
            case .all: return "All Variables"
            case .booleans: return "Boolean Flags"
            case .numbers: return "Numbers & Counters"
            case .strings: return "Text Values"
            case .arrays: return "String Arrays"
            case .dates: return "Dates & Times"
            case .enums: return "Custom Enums"
            }
        }

        public var iconName: String {
            switch self {
            case .all: return "list.bullet"
            case .booleans: return "checkmark.circle"
            case .numbers: return "number"
            case .strings: return "textformat"
            case .arrays: return "list.bullet.rectangle"
            case .dates: return "calendar"
            case .enums: return "slider.horizontal.3"
            }
        }
    }

    public enum Action {
        case onAppear
        case discoveryCompleted([AppStorageVariable])
        case sectionSelected(SectionType)
        case searchTextChanged(String)
        case updateVariable(id: String, value: AppStorageVariable.AppStorageValue)
        case refreshVariables
    }

    public init() {}

    public var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .onAppear:
                state.isLoading = true
                return .run { send in
                    let variables = AppStorageDiscovery.shared.discoverAppStorageVariables()
                    await send(.discoveryCompleted(variables))
                }

            case let .discoveryCompleted(variables):
                state.discoveredVariables = variables
                state.isLoading = false
                return .none

            case let .sectionSelected(section):
                state.selectedSection = section
                return .none

            case let .searchTextChanged(text):
                state.searchText = text
                return .none

            case let .updateVariable(id, value):
                if let index = state.discoveredVariables.firstIndex(where: { $0.id == id }) {
                    state.discoveredVariables[index].currentValue = value
                    AppStorageDiscovery.shared.writeValue(value, to: state.discoveredVariables[index].keyName)
                }
                return .none

            case .refreshVariables:
                return .send(.onAppear)
            }
        }
    }
}
