import AppSessionCountClient
import BackendEnvironmentClient
import ClerkClient
import ComposableArchitecture
import DebugUtilities
import RageshakeClient
import StatsigClient
import SwiftUI
#if DEBUG
    import FLEX
#endif

@Reducer
public struct DebugMenu {
    @Reducer(state: .equatable)
    public enum Destination {
        case featureFlags
        case internalViewPicker
        case appStorageMenu(AppStorageMenuReducer)
        case alert(AlertState<Destination.Alert>)

        public enum Alert: Equatable {
            case confirmEnvironmentChange(BackendEnvironment)
        }
    }

    public static var isAllowed: Bool {
        let env = BackendEnvironmentProvider.currentConfiguration().statsigEnvironment
        switch env {
        case "staging":
            return true
        case "production":
            return isDebugEnvironment || FeatureFlag.legacy.iosEnableStaffShakeToReport
        default:
            return false
        }
    }

    static var isDebugEnvironment: Bool {
        #if DEBUG
            return true
        #else
            return false
        #endif
    }

    @ObservableState
    public struct State: Equatable {
        @Presents public var destination: Destination.State?

        var offsetX: CGFloat = .zero
        var offsetY: CGFloat = .zero
        var panelWidth: CGFloat = UIScreen.main.bounds.width - (20 * 2)
        var panelHeight: CGFloat = UIScreen.main.bounds.height * 0.6
        var isPanelShown = false
        var isMinimized = false
        var panelType: DebugMenuPanelType = .panelSelector

        var onDeviceFileExplorerRootDirectory: OnDeviceFileDataModel.RootDirectory = .documents
        var onDeviceFileExplorerFileSelection: OnDeviceFileDataModel?
        var onDeviceFileExplorerPathSelections: [String] = []
        var onDeviceFileExplorereDataModels: [OnDeviceFileDataModel] = []

        var trackedMetricsEvents: [DebugAnalyticsEvent] = []
        var appSessionData: AppSessionData = AppSessionData()
        var selectedUserForDetails: String?
        var currentEnvironment: BackendEnvironment = BackendEnvironmentProvider.currentEnvironment()

        public init() {}
    }

    public enum Action: BindableAction {
        case destination(PresentationAction<Destination.Action>)
        case binding(BindingAction<State>)
        case onShake
        case onDrag(CGSize)
        case onResize(CGSize)
        case minimize
        case expand
        case toggleExpansion
        case closePanel(animated: Bool)
        case openPanelSelector
        case selectedPanel(DebugMenuPanelType)

        case reloadOnDeviceFileExplorer
        case selectedRootDirectory(_ directory: OnDeviceFileDataModel.RootDirectory)
        case selectedDirectoryOnFileExplorer(_ fileData: OnDeviceFileDataModel)
        case selectedDocumentOnFileExplorer(_ fileData: OnDeviceFileDataModel)
        case exitFileViewer
        case backUpOneDirectory

        case addAnalyticsEvent(DebugAnalyticsEvent)
        case clearAnalyticsEvents
        case resetAppSessions
        case refreshAppSessionData
        case appSessionDataLoaded(AppSessionData)
        case environmentButtonTapped(BackendEnvironment)
        case refreshEnvironment
    }

    @Dependency(StatsigClient.self) var statsigClient
    @Dependency(RageshakeClient.self) var rageshakeClient
    @Dependency(\.appSessionCountClient) var appSessionCountClient
    @Dependency(BackendEnvironmentClient.self) var backendEnvironmentClient
    @Dependency(ClerkClient.self) var clerkClient

    public init() {}

    public var body: some ReducerOf<Self> {
        BindingReducer()

        Reduce<State, Action> { state, action in
            switch action {
            case .refreshEnvironment:
                state.currentEnvironment = BackendEnvironmentProvider.currentEnvironment()
                return .none

            case let .environmentButtonTapped(environment):
                guard environment != state.currentEnvironment else { return .none }

                // Show confirmation alert
                state.destination = .alert(
                    AlertState {
                        TextState("Change Backend Environment")
                    } actions: {
                        ButtonState(role: .destructive, action: .send(.confirmEnvironmentChange(environment))) {
                            TextState("Sign Out")
                        }
                        ButtonState(role: .cancel) {
                            TextState("Cancel")
                        }
                    } message: {
                        TextState("You must sign out and manually restart the app in order to change the backend environment.")
                    }
                )
                return .none

            case let .addAnalyticsEvent(event):
                state.trackedMetricsEvents.append(event)
                return .none

            case .clearAnalyticsEvents:
                state.trackedMetricsEvents = []
                return .none

            case .resetAppSessions:
                return .run { send in
                    await appSessionCountClient.resetSessionData()
                    await send(.refreshAppSessionData)
                }

            case .refreshAppSessionData:
                return .run { send in
                    let sessionData = await appSessionCountClient.getSessionData()
                    await send(.appSessionDataLoaded(sessionData))
                }

            case let .appSessionDataLoaded(sessionData):
                state.appSessionData = sessionData
                return .none

            case .onShake:
                withAnimation {
                    state.isPanelShown = true
                }
                return .merge(
                    .send(.refreshAppSessionData),
                    .send(.refreshEnvironment)
                )

            case let .onDrag(translation):
                state.offsetX += translation.width
                state.offsetY += translation.height
                return .none

            case let .onResize(translation):
                let newWidth = state.panelWidth + translation.width
                let newHeight = state.panelHeight + translation.height
                state.panelWidth = max(newWidth, 150.0)
                state.panelHeight = max(newHeight, 100.0)
                return .none

            case .toggleExpansion:
                if state.isMinimized {
                    return .send(.expand)
                } else {
                    return .send(.minimize)
                }

            case .minimize:
                state.isMinimized = true
                return .none

            case .expand:
                state.isMinimized = false
                return .none

            case .openPanelSelector:
                state.panelType = .panelSelector
                return .none

            case .reloadOnDeviceFileExplorer:
                let rootDirectory = state.onDeviceFileExplorerRootDirectory.url
                let selections = state.onDeviceFileExplorerPathSelections

                state.onDeviceFileExplorereDataModels =
                    OnDeviceFileDataModel
                        .loadFileDataModels(
                            root: rootDirectory,
                            subPathSelections: selections
                        )
                return .none

            case let .selectedPanel(panelType):
                switch panelType {
                case .panelSelector:
                    return .none

                case .trackedMetrics:
                    state.panelType = panelType
                    return .none

                case .appSessions:
                    state.panelType = panelType
                    return .send(.refreshAppSessionData)

                case .featureToggles:
                    state.destination = .featureFlags
                    return .none

                case .environment:
                    state.panelType = panelType
                    return .send(.refreshEnvironment)

                case .onDeviceFileExplorer:
                    state.panelType = panelType
                    return .send(.reloadOnDeviceFileExplorer)

                case .internalViews:
                    state.destination = .internalViewPicker
                    return .none

                case .reportBug:
                    return .merge(
                        .send(.closePanel(animated: false)),
                        .run { _ in
                            await MainActor.run {
                                rageshakeClient.show()
                            }
                        }
                    )

                case .fileViewerPanel:
                    state.panelType = panelType
                    return .none

                case .flex:
                    #if DEBUG
                        FLEXManager.shared.showExplorer()
                    #endif
                    return .send(.closePanel(animated: true))

                case .appStorage:
                    state.destination = .appStorageMenu(AppStorageMenuReducer.State())
                    return .none
                }

            case let .selectedDirectoryOnFileExplorer(fileData):
                state.onDeviceFileExplorerPathSelections
                    .append(fileData.lastComponent)
                return .send(.reloadOnDeviceFileExplorer)

            case .exitFileViewer:
                state.onDeviceFileExplorerFileSelection = nil
                state.panelType = .onDeviceFileExplorer
                return .none

            case let .selectedDocumentOnFileExplorer(fileData):
                state.panelType = .fileViewerPanel
                state.onDeviceFileExplorerFileSelection = fileData
                return .none

            case let .selectedRootDirectory(rootDirectory):
                state.onDeviceFileExplorerFileSelection = nil
                state.onDeviceFileExplorereDataModels = []
                state.onDeviceFileExplorerPathSelections = []
                state.onDeviceFileExplorerRootDirectory = rootDirectory
                return .send(.reloadOnDeviceFileExplorer)

            case .backUpOneDirectory:
                var selections = state.onDeviceFileExplorerPathSelections
                if !selections.isEmpty {
                    selections.removeLast()
                }
                state.onDeviceFileExplorerPathSelections = selections
                return .send(.reloadOnDeviceFileExplorer)

            case let .closePanel(animated):
                state.destination = nil
                if animated {
                    withAnimation {
                        state.isPanelShown = false
                    }
                } else {
                    state.isPanelShown = false
                }
                return .none

            case let .destination(.presented(.alert(.confirmEnvironmentChange(environment)))):
                state.currentEnvironment = environment

                return .run { send in
                    if environment == .production {
                        await backendEnvironmentClient.resetOverride()
                    } else {
                        await backendEnvironmentClient.setEnvironment(environment)
                    }

                    do {
                        try await clerkClient.signOut()
                    } catch {
                        print("Error signing out: \(error)")
                    }

                    // Force app quit to ensure environment change takes effect
                    fatalError("Forcing app quit from DebugMenu after environment change; This is intentional")
                }

            case .binding,
                 .destination:
                // Catch-all
                return .none
            }
        }
        .ifLet(\.$destination, action: \.destination)
    }
}
