import APIClient
import ComponentLibrary
import ComposableArchitecture
import FeatureToasts
import Localization
import PaywallClient
import SwiftUI
import Utilities

@Reducer
public struct TopUp {
    @ObservableState
    public struct State: Equatable {
        public var selectedOptionIndex = 0
        public var loadState: LoadState = .loading
        public var purchasing: CreditsPackage?
        public var packages = [CreditsPackage]()

        public init() {}
    }

    public enum Action {
        case task
        case loadResult(Result<[CreditsPackage], Error>)
        case dismiss
        case purchase
        case purchaseResult(Result<Void, Error>)
        case creditsRefreshResult(Result<SubscriptionInfoResponse, Error>)
        case optionTapped(CreditsPackage)
        case delegate(Delegate)

        public enum Delegate {
            case toastAfter(ToastReducer.State.ToastType)
            case setBillingInfoAfter(SubscriptionInfoResponse)
            case refreshCreditsAfter
        }
    }

    @Dependency(\.dismiss) var dismiss
    @Dependency(PaywallClient.self) private var paywall

    public init() {}

    public var toastMessage: AttributedString = attributedString(
        for: L10n.FeatureTopUp.successMessage,
        typography: TypographyV1.bodySmall
    )

    public var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .task:
                return .run { send in
                    await send(.loadResult(Result(catching: { try await paywall.creditsPackages() })))
                }

            case .loadResult(.success(let packages)):
                state.packages = packages
                state.loadState = .loaded
                return .none

            case .loadResult(.failure):
                state.loadState = .failed(L10n.FeatureTopUp.noAvailablePackages)
                return .none

            case .optionTapped(let package):
                state.selectedOptionIndex = state.packages.firstIndex(of: package) ?? 0
                return .none

            case .dismiss:
                return .run { _ in await self.dismiss() }

            case .purchase:
                let package = state.packages[state.selectedOptionIndex]
                state.purchasing = package
                return .run { send in
                    await send(.purchaseResult(Result(catching: { try await paywall.purchaseCreditsPackage(package) })))
                }

            case .purchaseResult(.success):
                return .run { send in
                    await send(.delegate(.refreshCreditsAfter))
                }

            case .purchaseResult(.failure(PaywallError.cancelled)):
                state.purchasing = nil
                return .none

            case .purchaseResult(.failure(let error)):
                log.telemetry.error(error)
                state.purchasing = nil
                return .merge(
                    .send(.delegate(.toastAfter(.warning(L10n.FeatureTopUp.actionFailed, .string(""), position: .bottom)))),
                    .send(.dismiss)
                )

            case .creditsRefreshResult(.success(let billingInfo)):
                state.purchasing = nil
                return .concatenate(
                    .send(.dismiss),
                    .send(.delegate(.setBillingInfoAfter(billingInfo))),
                    .send(.delegate(.toastAfter(.success(L10n.FeatureTopUp.successTitle, .attributedString(toastMessage), position: .bottom, trailingView: .dismiss))))
                )

            case .creditsRefreshResult(.failure):
                state.purchasing = nil
                return .concatenate(
                    .send(.dismiss),
                    .send(.delegate(.toastAfter(.success(L10n.FeatureTopUp.successTitle, .attributedString(toastMessage), position: .bottom, trailingView: .dismiss))))
                )

            case .delegate:
                return .none
            }
        }
    }
}

public struct TopUpScreen: View {
    @Bindable var store: StoreOf<TopUp>

    public init(store: StoreOf<TopUp>) {
        self.store = store
    }

    public var body: some View {
        Group {
            switch store.loadState {
            case .loading:
                LoadingView()
            case .loaded:
                loadedView
            case .failed(let message):
                FailedView(title: L10n.FeatureClipDetail.errorTitle, message: message, buttonTitle: L10n.FeatureClipDetail.retry, action: {})
            }
        }
        .padding(.horizontal, 12)
        .padding(.bottom, bottomPadding)
        .background(Color.SemanticV1.backgroundPrimary)
        .task {
            store.send(.task)
        }
    }

    private var loadedView: some View {
        VStack(alignment: .leading, spacing: 0) {
            VStack(alignment: .leading, spacing: 8) {
                Text(L10n.FeatureTopUp.title)
                    .typographyV1(.headline1.editorialNewRegular())
                    .foregroundStyle(Color.SemanticV1.textPrimary)
                Text(L10n.FeatureTopUp.subtitle)
                    .typographyV1(.body1.neueMontrealRegular())
                    .foregroundStyle(Color.SemanticV1.textSecondary)
                    .frame(height: 24)
            }
            .padding(.vertical, 32)
            ForEach(Array(store.packages.enumerated()), id: \.element.id) { index, package in
                HStack(spacing: 24) {
                    RadioButton(isSelected: store.selectedOptionIndex == index)
                    Text("\(package.title) - \(package.price)")
                        .typographyV1(.body1)
                        .foregroundStyle(Color.SemanticV1.textBrand)
                    Spacer()
                }
                .contentShape(.rect)
                .padding(.vertical, 16)
                .onTapGesture {
                    store.send(.optionTapped(package))
                }
            }
            Spacer()
            PrimaryButtonV1(
                title: L10n.FeatureTopUp.continue,
                isLoading: store.purchasing != nil,
                action: { store.send(.purchase) }
            )
            .pillButtonSizeV1(.large)
        }
    }

    var bottomPadding: CGFloat {
        @Shared(.inMemory(.isCompactPlayerVisible)) var isCompactPlayerVisible = false
        let tabBarHeight: CGFloat = CustomBottomBarConstants.tabBarHeight
        let compactPlayerHeight: CGFloat = OmniPlayerConstants.compactPlayerHeight
        let bottomSafeAreaHeight: CGFloat = isCompactPlayerVisible ? tabBarHeight + compactPlayerHeight : tabBarHeight
        return bottomSafeAreaHeight + 12
    }
}
