import APIClient
import ComponentLibrary
import ComposableArchitecture
import FeaturePhotoPicker
import FeatureSocial
import FeatureToasts
import Localization
import StatsigClient
import SwiftUI
import UserEventBusClient
import Utilities

// swiftlint:disable file_length

@Reducer
public struct Account {
    @Reducer(state: .equatable)
    public enum Destination {
        case alert(AlertState<Alert>)
        case editAvatar(PhotoPicker)
        case addPhoneNumber(PhoneAddNumberInPlatform)

        public enum Alert {
            case deleteAccount
            case deletePhoneNumber
        }
    }

    @ObservableState
    public struct State: Equatable {
        public enum UsernameStatus {
            case available, unavailable, pending
        }

        @Presents public var destination: Destination.State?

        var isDeleting = false
        var isUpdating = false
        var isUpdatingAvatar = false

        var avatarUrl: String?
        var displayName: String = ""
        var username: String = ""
        var userId: String?
        var phoneNumber: String?

        // Required for the `Add Phone Number` pipeline
        @Shared var me: Me
        @Shared(.inMemory(.isCompactPlayerVisible)) var isCompactPlayerVisible = false

        var usernameStatus: UsernameStatus = .available

        var isContactSyncEnabled: Bool {
            FeatureFlag.legacy.contactSync
        }

        let showBackButton: Bool

        var saveButtonBottomPadding: 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 + 4
        }

        public init(me: Shared<Me>, showBackButton: Bool = true) {
            self._me = me
            self.showBackButton = showBackButton
        }
    }

    public enum Action: BindableAction {
        case onAppear
        case back
        case destination(PresentationAction<Destination.Action>)
        case deleteAccountTapped
        case editAvatarTapped
        case deleteAccountResponse(Result<Void, Error>)
        case deletePhoneNumberResponse(Result<Void, Error>)
        case binding(BindingAction<State>)

        case getMe
        case updateMe

        case getMeResponse(Result<Me, Error>)
        case updateMeResponse(Result<Void, Error>)

        case addPhoneNumberTapped
        case deletePhoneNumberTapped
    }

    @Dependency(APIClient.self) var apiClient
    @Dependency(APIClientV2.self) var api
    @Dependency(\.telemetryClient) var telemetry
    @Dependency(\.dismiss) var dismiss
    @Dependency(\.mainQueue) var mainQueue
    @Dependency(\.userEventBus.send) var sendUserEvent
    @Dependency(\.toastClient.show) var showToast

    public init() {}

    public var body: some ReducerOf<Self> {
        BindingReducer()
        Reduce { state, action in
            switch action {
            case .onAppear:
                return .send(.getMe)

            case .getMe:
                return .run { send in
                    await send(.getMeResponse(Result(catching: { try await api.getMe() })))
                }

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

            case .editAvatarTapped:
                state.destination = .editAvatar(.init())
                return .none

            case .deleteAccountTapped:
                state.destination = .alert(.init(
                    title: { TextState(L10n.FeatureSettings.deleteAccountTitle) },
                    actions: {
                        ButtonState(role: .destructive, action: .deleteAccount) { TextState(L10n.FeatureSettings.deleteAccountButton) }
                        ButtonState(role: .cancel) { TextState(L10n.FeatureSettings.cancel) }
                    },
                    message: { TextState(L10n.FeatureSettings.deleteAccountMessage) }
                ))
                return .none

            case .destination(.presented(.alert(.deleteAccount))):
                state.isDeleting = true
                return .run { send in
                    await send(.deleteAccountResponse(Result(catching: { try await apiClient.deleteAccount() })))
                }

            case .destination(.presented(.alert(.deletePhoneNumber))):
                guard let phoneNumber = state.phoneNumber else { return .none }
                return .run { send in
                    await send(.deletePhoneNumberResponse(Result(catching: { try await apiClient.deletePhoneNumber(phoneNumber) })))
                }

            case .deleteAccountResponse(.success):
                // Handled in App Coordinator
                state.isDeleting = false
                sendUserEvent(.signOut)
                return .run { _ in await self.dismiss() }

            case .deleteAccountResponse(.failure(let error)):
                log.telemetry.error(error)
                state.isDeleting = false
                return .none

            case .deletePhoneNumberResponse(.success):
                return .send(.getMe)

            case .deletePhoneNumberResponse(.failure(let error)):
                state.destination = .alert(.init(
                    title: { TextState(L10n.FeatureSettings.errorTitle) },
                    actions: {
                        ButtonState { TextState(L10n.FeatureSettings.errorButton) }
                    },
                    message: { TextState(error.underlyingError) }

                ))
                return .none

            case .destination(.presented(.editAvatar(.selectImage(.success(let image))))):
                guard let base64Image = image.squared()?.jpegData(compressionQuality: 0.7)?.base64EncodedString() else {
                    assertionFailure("Could not encode selected image")
                    return .none
                }

                state.isUpdatingAvatar = true

                return .run { [displayName = state.displayName, username = state.username] send in
                    await send(.updateMeResponse(
                        Result(catching: { try await apiClient.updateMe(.init(displayName: displayName, handle: username, avatarImageUrl: "data:image/png;base64,\(base64Image)")) }))
                    )
                }

            case .destination(.presented(.addPhoneNumber(.delegate(.validationSuccess)))):
                return .send(.getMe)

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

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

            case .getMeResponse(.success(let me)):
                state.avatarUrl = me.user.avatarImageUrl ?? ""
                state.displayName = me.user.displayName ?? ""
                state.username = me.user.handle
                state.userId = me.user.id
                state.phoneNumber = me.user.phoneNumber
                state.$me.withLock { $0 = me }
                state.isUpdatingAvatar = false
                return .none

            case .getMeResponse(.failure(let error)):
                log.telemetry.error(error)
                state.isUpdatingAvatar = false
                return .none

            case .updateMe:
                state.isUpdating = true
                let displayName = state.displayName
                let username = state.username

                return .run { send in
                    await send(.updateMeResponse(Result(catching: { try await apiClient.updateMe(.init(displayName: displayName, handle: username)) })))
                }

            case .updateMeResponse(.success):
                state.usernameStatus = .available
                state.isUpdating = false

                if state.isUpdatingAvatar {
                    state.isUpdatingAvatar = false
                }
                return .run { send in
                    await send(.getMe)
                    showToast(.success(L10n.FeatureSettings.profileUpdated, position: .bottom))
                }

            case .updateMeResponse(.failure(UserError.usernameAlreadyTaken)):
                state.isUpdating = false
                state.isUpdatingAvatar = false
                state.usernameStatus = .unavailable
                return .none

            case .updateMeResponse(.failure(let error)):
                log.telemetry.error(error)
                state.isUpdating = false
                state.isUpdatingAvatar = false
                state.usernameStatus = .pending
                return .none

            case .addPhoneNumberTapped:
                state.destination = .addPhoneNumber(.init(me: state.me))
                return .none

            case .deletePhoneNumberTapped:
                state.destination = .alert(.init(
                    title: { TextState(L10n.FeatureSettings.deletePhoneNumberTitle) },
                    actions: {
                        ButtonState(role: .destructive, action: .deletePhoneNumber) { TextState(L10n.FeatureSettings.deletePhoneNumberButton) }
                        ButtonState(role: .cancel) { TextState(L10n.FeatureSettings.cancel) }
                    },
                    message: { TextState(L10n.FeatureSettings.deletePhoneNumberMessage) }
                ))
                return .none
            }
        }
        .ifLet(\.$destination, action: \.destination)
    }
}

public struct AccountScreen: View {
    enum FocusField {
        case name, username
    }

    @FocusState private var focusedField: FocusField?

    @Bindable var store: StoreOf<Account>
    @State var detentHeight: CGFloat = 0

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

    public var body: some View {
        ScrollView {
            VStack(spacing: 42) {
                avatar

                fields

                if !store.isContactSyncEnabled {
                    Spacer()
                }

                saveButton

                deleteButton
            }
            .padding(12)
        }
        .scrollDismissesKeyboard(.immediately)
        .animation(.easeInOut, value: store.avatarUrl)
        .background(Color.SemanticV1.backgroundPrimary)
        .toolbarBackground(.visible, for: .navigationBar)
        .alert($store.scope(state: \.destination?.alert, action: \.destination.alert))
        .sheet(item: $store.scope(state: \.destination?.editAvatar, action: \.destination.editAvatar)) { store in
            PhotoPickerView(store: store)
                .selfSizingPresentation()
        }
        .fullScreenCover(item: $store.scope(state: \.destination?.addPhoneNumber, action: \.destination.addPhoneNumber)) { store in
            PhoneAddNumberInPlatformView(store: store)
        }
        .navigationBarTitleDisplayMode(.inline)
        .modifier(if: store.showBackButton) {
            $0.customBackButton(background: Color.SemanticV1.backgroundQuaternary, action: { store.send(.back) })
        }
        .toolbar {
            ToolbarItem(placement: .principal) {
                ToolbarTitle(L10n.FeatureSettings.profileTitle)
            }

            if store.isUpdatingAvatar {
                ToolbarItem(placement: .topBarTrailing) {
                    ProgressView()
                        .progressViewStyle(.circular)
                }
            }
        }
        .onAppear { store.send(.onAppear) }
        .onTapGesture { focusedField = nil }
    }

    private var avatar: some View {
        RemoteImage(url: store.avatarUrl, fallbackId: store.userId)
            .frame(width: 260, height: 260)
            .scaledToFill()
            .transition(.opacity)
            .overlay {
                if store.isUpdatingAvatar {
                    ProgressView()
                        .progressViewStyle(.circular)
                }
            }
            .clipShape(Circle())
            .overlay(alignment: .bottomTrailing) {
                Button {
                    store.send(.editAvatarTapped)
                } label: {
                    Image.Icon.edit
                        .foregroundStyle(Color.SemanticV1.iconPrimary)
                        .padding(10)
                        .background(Circle().fill(Color.SemanticV1.backgroundTertiary))
                }
                .padding([.leading, .bottom], 24)
            }
            .padding(.top, 24)
    }

    // TODO: could be improved by using .submitLabel and .onSubmit
    private var fields: some View {
        VStack(spacing: 8) {
            VStack(alignment: .leading, spacing: 2) {
                Text(L10n.FeatureSettings.name)
                    .typographyV1(.body1)
                    .foregroundStyle(Color.SemanticV1.textPrimary)

                TextField(L10n.FeatureSettings.namePlaceholder, text: $store.displayName.limit(40))
                    .textContentType(.name)
                    .textInputAutocapitalization(.words)
                    .typographyV1(.body2)
                    .foregroundStyle(Color.SemanticV1.textSecondary)
                    .accentColor(.SemanticV1.textLink)
                    .focused($focusedField, equals: .name)
            }
            .padding(.vertical, 12)
            .padding(.horizontal, 20)
            .background(RoundedRectangle(cornerRadius: 8).fill(Color.SemanticV1.backgroundTertiary))

            HStack(alignment: .center) {
                VStack(alignment: .leading, spacing: 2) {
                    Text(L10n.FeatureSettings.username)
                        .typographyV1(.body1)
                        .foregroundStyle(Color.SemanticV1.textPrimary)

                    HStack(spacing: 0) {
                        Text("@")
                            .typographyV1(.body2)
                            .foregroundStyle(Color.SemanticV1.textSecondary)

                        TextField(L10n.FeatureSettings.usernamePlaceholder, text: $store.username.limit(40))
                            .textContentType(.username)
                            .textInputAutocapitalization(.never)
                            .autocorrectionDisabled()
                            .typographyV1(.body2)
                            .foregroundStyle(Color.SemanticV1.textSecondary)
                            .accentColor(.SemanticV1.textLink)
                            .focused($focusedField, equals: .username)
                    }
                }

                switch store.usernameStatus {
                case .pending:
                    EmptyView()

                case .available:
                    Image.Icon.checkV1
                        .resizable()
                        .frame(width: 24, height: 24)
                        .foregroundColor(Color.SemanticV1.iconPrimary)
                        .transition(.opacity)

                case .unavailable:
                    Image.Icon.warning
                        .resizable()
                        .frame(width: 24, height: 24)
                        .foregroundColor(Color.SemanticV1.iconPrimary)
                        .transition(.opacity)
                }
            }
            .animation(.default, value: store.usernameStatus)
            .padding(.vertical, 12)
            .padding(.horizontal, 20)
            .background(RoundedRectangle(cornerRadius: 8).fill(Color.SemanticV1.backgroundTertiary))

            if store.isContactSyncEnabled {
                Button {
                    store.send(.addPhoneNumberTapped)
                } label: {
                    HStack(alignment: .center) {
                        VStack(alignment: .leading, spacing: 2) {
                            Text(L10n.FeatureSettings.phoneNumber)
                                .typographyV1(.body1)
                                .foregroundStyle(Color.SemanticV1.textPrimary)

                            if let phoneNumber = store.phoneNumber {
                                Text(phoneNumber.formatPhoneNumber(format: .international))
                                    .typographyV1(.body2thin)
                                    .foregroundStyle(Color.SemanticV1.textSecondary)
                                    .textContentType(.telephoneNumber)
                            } else {
                                Text(L10n.FeatureSettings.phoneNumberPlaceholder)
                                    .typographyV1(.body2thin)
                                    .foregroundStyle(Color.SemanticV1.textTertiary)
                                    .textContentType(.telephoneNumber)
                            }
                        }

                        Spacer()

                        Button {
                            store.send(.deletePhoneNumberTapped)
                        } label: {
                            Image.Icon.trashV1
                                .resizable()
                                .foregroundColor(Color.SemanticV1.iconWarning)
                        }
                        .contentShape(.rect)
                        .frame(width: 24, height: 24)
                        .buttonStyle(ScaleButtonStyle())
                        .opacity(store.phoneNumber == nil ? 0 : 1)
                    }
                }
                .padding(.vertical, 12)
                .padding(.horizontal, 20)
                .background(RoundedRectangle(cornerRadius: 8).fill(Color.SemanticV1.backgroundTertiary))
                .buttonStyle(.plain)
            }
        }
    }

    private var saveButton: some View {
        PrimaryButtonV1(
            title: L10n.FeatureSettings.save,
            isLoading: store.isUpdating,
            colorCombination: .dark,
            preferredSize: .large,
            action: { store.send(.updateMe) }
        )
        .padding(12)
        .background(Color.SemanticV1.backgroundPrimary)
    }

    private var deleteButton: some View {
        HStack(alignment: .center, spacing: 8) {
            if store.isDeleting {
                ProgressView()
                    .tint(Color.SemanticV1.iconWarning)
                    .controlSize(.mini)
            }

            Button(L10n.FeatureSettings.deleteAccount) {
                store.send(.deleteAccountTapped)
            }
            .typographyV1(.button1)
            .foregroundStyle(Color.SemanticV1.textWarning)
        }
        .animation(.default, value: store.isDeleting)
        .padding(.horizontal, 24)
    }
}
