import APIClient
import Charts
import ComponentLibrary
import ComposableArchitecture
import EventBusClient
import Foundation
import Localization
import OrderedCollections
import PaywallClient
import StatsigClient
import SunoModelClient
import SwiftUI
import Utilities

@Reducer
public struct PaywallV1 {
    @Reducer(state: .equatable)
    public enum Destination {
        @Reducer public struct LoadingState {}
        @Reducer public struct ErrorState {
            @ObservableState
            public struct State: Equatable {
                public enum ActionType {
                    case retry
                    case contactSupport
                }

                let message: String
                let buttonTitle: String
                let action: ActionType
            }
        }

        case loading(LoadingState)
        case loaded(PaywallLoaded)
        case error(ErrorState)
    }

    @ObservableState
    public struct State: Equatable {
        @Presents public var destination: Destination.State? = .loading(.init())
        var isRestoring = false
        var isRefreshingAfterPurchase = false
        var subscriptions: [Subscription] = []
        @Shared(.inMemory(.billingInfo)) var billingInfo: SubscriptionInfoResponse?
        var purchases: [Entitlement] = []
        var appStoreCurrencyCode: String?

        public init() {}
    }

    public enum Action {
        case destination(PresentationAction<Destination.Action>)

        case dismiss
        case loadSubscriptions
        case restoreTapped
        case contactSupport
        case subscriptionsResult(Result<SubscriptionsConfiguration, Error>)
        case billingInfoResult(Result<SubscriptionInfoResponse, Error>)
        case purchasesResult(Result<[Entitlement], Error>)
    }

    @Dependency(APIClient.self) private var apiClient
    @Dependency(APIClientV2.self) private var api
    @Dependency(PaywallClient.self) private var paywall
    @Dependency(\.openURL) var openURL
    @Dependency(\.telemetryClient) var telemetry
    @Dependency(\.dismiss) var dismiss
    @Dependency(SunoModelClient.self) var sunoModelClient
    @Dependency(\.eventBus.getBillingChannel) var getBillingChannel

    public init() {}

    public var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .loadSubscriptions:
                return .run { [billingInfo = state.billingInfo] send in
                    await send(.subscriptionsResult(Result(catching: { try await paywall.subscriptions(billingInfo) })))
                }

            case .subscriptionsResult(.success(let subscriptionsConfiguration)):
                state.subscriptions = subscriptionsConfiguration.subscriptions
                state.appStoreCurrencyCode = subscriptionsConfiguration.currencyCode
                return .run { send in
                    await send(.purchasesResult(Result(catching: { try await paywall.syncPurchases() })))
                }

            case .subscriptionsResult(.failure(let error)):
                state.destination = .error(.init(
                    message: L10n.FeaturePaywall.errorSubscriptions,
                    buttonTitle: L10n.FeaturePaywall.retry,
                    action: .retry
                ))
                log.telemetry.error(error)
                return .none

            case let .purchasesResult(.success(purchases)):
                state.isRestoring = false
                state.purchases = purchases
                return .run { send in
                    await send(.billingInfoResult(Result(catching: { try await api.getBillingInfo() })))
                }

            case .purchasesResult(.failure(let error)):
                state.isRestoring = false
                state.destination = .error(.init(
                    message: L10n.FeaturePaywall.errorPurchases,
                    buttonTitle: L10n.FeaturePaywall.retry,
                    action: .retry
                ))
                log.telemetry.error(error)
                return .none

            case let .billingInfoResult(.success(billingInfo)):
                state.$billingInfo.withLock { $0 = billingInfo }
                if state.isRefreshingAfterPurchase {
                    return .run { send in
                        await send(.destination(.presented(.loaded(.destination(.presented(.topUp(.creditsRefreshResult(.success(billingInfo)))))))))
                    }
                }
                state.isRestoring = false
                state.destination = .loaded(.init(
                    subscriptions: state.subscriptions,
                    appStoreCurrencyCode: state.appStoreCurrencyCode,
                    purchases: state.purchases,
                    billingInfo: billingInfo
                ))

                getBillingChannel().queue(.billingInfoUpdated(billingInfo))
                return .none

            case .billingInfoResult(.failure(let error)):
                if state.isRefreshingAfterPurchase {
                    return .run { send in
                        await send(.destination(.presented(.loaded(.destination(.presented(.topUp(.creditsRefreshResult(.failure(error)))))))))
                    }
                }
                state.isRestoring = false
                state.destination = .error(.init(
                    message: L10n.FeaturePaywall.errorPurchases,
                    buttonTitle: L10n.FeaturePaywall.retry,
                    action: .retry
                ))
                log.telemetry.error(error)
                return .none

            case .restoreTapped:
                state.isRestoring = true
                return .run { send in
                    await send(.purchasesResult(Result(catching: { try await paywall.restore() })))
                }

            case .contactSupport:
                let url = URL(string: "mailto:support@suno.com")!
                return .run { _ in
                    await openURL(url)
                }

            case .destination(.presented(.loaded(.purchaseResult(.success((_, let purchases)))))):
                return .send(.purchasesResult(.success(purchases)))

            case .destination(.presented(.loaded(.purchaseResult(.failure(PaywallError.cancelled))))):
                return .none

            case .destination(.presented(.loaded(.purchaseResult(.failure)))):
                state.destination = .error(.init(
                    message: L10n.FeaturePaywall.errorPurchase,
                    buttonTitle: L10n.FeaturePaywall.contactSupport,
                    action: .contactSupport
                ))
                return .none

            case .destination(.presented(.loaded(.delegate(.refreshCreditsAfter)))):
                state.isRefreshingAfterPurchase = true
                return .run { send in
                    await send(.billingInfoResult(Result(catching: { try await api.getBillingInfo() })))
                }

            case .destination:
                // Catch-all
                return .none

            case .dismiss:
                if let billingInfo = state.billingInfo {
                    sunoModelClient.setUserAccess(userAccess: billingInfo.sunoModelUserAccess)
                }
                return .run { _ in await self.dismiss() }
            }
        }
        .ifLet(\.$destination, action: \.destination)
    }
}

public struct PaywallScreenV1: View {
    @Bindable private var store: StoreOf<PaywallV1>

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

    public var body: some View {
        ZStack {
            if let destination = store.destination {
                switch destination {
                case .loading:
                    ProgressView()
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                        .onAppear { store.send(.loadSubscriptions) }

                case .loaded:
                    if let loadedStore = store.scope(state: \.destination?.loaded, action: \.destination.loaded) {
                        PaywallLoadedView(store: loadedStore)
                    }

                case .error:
                    if let errorStore = store.scope(state: \.destination?.error, action: \.destination.error) {
                        FailedView(title: L10n.FeaturePaywall.errorTitle, message: errorStore.message, buttonTitle: errorStore.buttonTitle) {
                            switch errorStore.action {
                            case .retry: store.send(.loadSubscriptions)
                            case .contactSupport: store.send(.contactSupport)
                            }
                        }
                    }
                }
            }
        }
        .navigationBarBackground(Color.SemanticV1.backgroundBrown)
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .principal) {
                Text(L10n.FeaturePaywall.navigationTitle)
                    .typographyV1(.navigationTitle)
                    .foregroundColor(Color.SemanticV1.textOnDark)
            }
            ToolbarItem(placement: .navigationBarTrailing) {
                Button {
                    store.send(.restoreTapped)
                } label: {
                    Text(L10n.FeaturePaywall.restore)
                        .typographyV1(.body1)
                        .foregroundStyle(Color.SemanticV1.textOnDark)
                        .opacity(store.isRestoring ? 0 : 1)
                        .overlay {
                            ProgressView()
                                .progressViewStyle(CircularProgressViewStyle(tint: .SemanticV1.textLink))
                                .opacity(store.isRestoring ? 1 : 0)
                        }
                }
                .buttonStyle(.borderedProminent)
                .tint(.SemanticV1.textLink)
            }
        }
    }

    private func errorView(_ message: String) -> some View {
        VStack {
            Text(L10n.FeaturePaywall.errorTitle)
                .typographyV1(.headline4)

            Text(message)
                .typographyV1(.body1)
                .foregroundStyle(Color.SemanticV1.textSecondary)
        }
        .padding()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

extension Subscription.Period {
    var title: String {
        switch self {
        case .day: L10n.FeaturePaywall.day
        case .week: L10n.FeaturePaywall.week
        case .month: L10n.FeaturePaywall.month
        case .year: L10n.FeaturePaywall.year
        }
    }
}

extension Subscription.SemanticPlan {
    var title: String {
        switch self {
        case .basic: L10n.FeaturePaywall.basicPlan
        case .pro: L10n.FeaturePaywall.proPlan
        case .premier: L10n.FeaturePaywall.premierPlan
        }
    }
}

extension SubscriptionInfoResponse {
    struct SunoCredits {
        var title: String
        var credits: Double
        var color: Color
    }

    var creditsData: [SunoCredits] {
        let used = monthlyUsage <= 0 ? 0 : Double(100 * monthlyUsage / monthlyLimit)
        let remaining = totalCreditsLeft <= 0 ? 1 : Double(100 * totalCreditsLeft / monthlyLimit)
        let data: [SunoCredits] = [
            .init(title: L10n.FeaturePaywall.usedCredits, credits: used, color: Color.SemanticV1.textLink.opacity(0.2)),
            .init(title: L10n.FeaturePaywall.remainingCredits, credits: remaining, color: Color.SemanticV1.textLink),
        ]
        return data
    }

    var paywallSubscriptionID: String {
        var periodPart: String {
            switch period {
            case "month": "monthly"
            case "year", "annual": "yearly"
            default: ""
            }
        }

        var planPart: String {
            switch plan?.level {
            case 10?: "pro"
            case 30?: "premier"
            default: ""
            }
        }
        return "\(periodPart).\(planPart)"
    }
}
