import APIClient
import BackendEnvironmentClient
import ClerkClient
import ComponentLibrary
import ComposableArchitecture
import Foundation
import Localization
import SwiftUI
import Utilities

@Reducer
public struct SignUp {
    public enum SignInMethod {
        case phone, apple, google, microsoft, discord
    }

    public enum Method {
        case create
        case login

        var title: String {
            switch self {
            case .create: return L10n.FeatureOnboarding.signUpCreateTitle
            case .login: return L10n.FeatureOnboarding.signUpLoginTitle
            }
        }
    }

    public enum FlowResult {
        case cancelled
        case phoneSignInSelected
        case authenticated(isFirstTimeUser: Bool)
        case authenticatedWithFullName(
            isFirstTimeUser: Bool,
            fullName: String
        )
    }

    @ObservableState
    public struct State: Equatable {
        public var isWorking: SignInMethod?
        var method: Method

        public init(method: Method) {
            self.method = method
        }
    }

    public enum Action {
        public enum Internal {
            case signInResponse(AuthenticationResult)
            case signInAndGetFullNameResponse(AuthenticationResult)
        }

        public enum Delegate {
            case onAppear
            case flowCompleted(FlowResult)
            case authenticationError
            case authMethodSelected(SignInMethod)
        }

        case onAppear
        case cancelTapped

        case signIn(SignInMethod)

        case `internal`(Internal)
        case delegate(Delegate)
    }

    @Dependency(ClerkClient.self) var clerk
    @Dependency(\.telemetryClient) var telemetry
    @Dependency(APIClient.self) var apiClient

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

            case .cancelTapped:
                return .send(.delegate(.flowCompleted(.cancelled)))

            case .signIn(.phone):
                UIImpactFeedbackGenerator(style: .medium).impactOccurred()
                return .concatenate(
                    .send(.delegate(.authMethodSelected(.phone))),
                    .send(.delegate(.flowCompleted(.phoneSignInSelected)))
                )

            case .signIn(.apple):
                UIImpactFeedbackGenerator(style: .medium).impactOccurred()
                state.isWorking = .apple
                return .concatenate(
                    .send(.delegate(.authMethodSelected(.apple))),
                    .run { send in
                        await send(.internal(.signInAndGetFullNameResponse(await clerk.signInWithApple())))
                    }
                )

            case .signIn(.google):
                UIImpactFeedbackGenerator(style: .medium).impactOccurred()
                state.isWorking = .google
                return .concatenate(
                    .send(.delegate(.authMethodSelected(.google))),
                    .run { send in
                        await send(.internal(.signInAndGetFullNameResponse(await clerk.signInWithGoogle())))
                    }
                )

            case .signIn(.microsoft):
                UIImpactFeedbackGenerator(style: .medium).impactOccurred()
                state.isWorking = .microsoft
                return .concatenate(
                    .send(.delegate(.authMethodSelected(.microsoft))),
                    .run { send in
                        await send(.internal(.signInResponse(await clerk.signInWithMicrosoft())))
                    }
                )

            case .signIn(.discord):
                UIImpactFeedbackGenerator(style: .medium).impactOccurred()
                state.isWorking = .discord
                return .concatenate(
                    .send(.delegate(.authMethodSelected(.discord))),
                    .run { send in
                        await send(.internal(.signInResponse(await clerk.signInWithDiscord())))
                    }
                )

            case .internal(.signInResponse(let authResult)):
                switch authResult {
                case .success(let info):
                    state.isWorking = nil
                    return .send(.delegate(.flowCompleted(.authenticated(isFirstTimeUser: info.isFirstTimeUser))))

                case .failure(let error):
                    return handleAuthenticationFailure(error: error, state: &state)
                }

            case .internal(.signInAndGetFullNameResponse(let authResult)):
                switch authResult {
                case .success(let info):
                    state.isWorking = nil

                    let flowResult: FlowResult = if let fullName = info.fullName {
                        .authenticatedWithFullName(isFirstTimeUser: info.isFirstTimeUser, fullName: fullName)
                    } else {
                        .authenticated(isFirstTimeUser: info.isFirstTimeUser)
                    }

                    return .send(.delegate(.flowCompleted(flowResult)))

                case .failure(let error):
                    return handleAuthenticationFailure(error: error, state: &state)
                }

            case .delegate:
                // Handled upstream
                return .none
            }
        }

        Analytics()
    }
}

// MARK: - Private Handlers

private extension SignUp {
    func handleAuthenticationFailure(
        error: ClerkClientError,
        state: inout State
    ) -> Effect<Action> {
        state.isWorking = nil

        if case .apiError(let apiError) = error {
            if let clerkTraceId = apiError.clerkTraceId {
                log.telemetry.error(error, message: "<ClerkTraceId: \(clerkTraceId)>")
            } else {
                log.telemetry.error(error)
            }

            switch apiError.errorCode {
            case .sessionExists:
                // If the auth session exists, we want to inform parent that we've authenticated
                // and are not a first time user
                return .send(.delegate(.flowCompleted(.authenticated(isFirstTimeUser: false))))

            default:
                break
            }
        }

        // Log anything that's not an API error
        log.telemetry.error(error)
        return .send(.delegate(.authenticationError))
    }
}

// MARK: - SignUpScreen

public struct SignUpScreen: View {
    var store: StoreOf<SignUp>

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

    public var body: some View {
        VStack(spacing: 0) {
            Spacer()

            Text(store.method.title)
                .typographyV1(.headline1)
                .foregroundStyle(Color.SemanticV1.textPrimary)
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.bottom, 8)

            Spacer()

            VStack(spacing: 32) {
                PrimaryButtonV1(
                    title: L10n.FeatureOnboarding.signUpPhone,
                    isLoading: store.isWorking == .phone,
                    colorCombination: primaryButtonV1ColorCombinationDark,
                    preferredSize: .medium,
                    leadingView: { Image.Icon.phone },
                    action: { store.send(.signIn(.phone)) }
                )

                HStack(spacing: 8) {
                    dividerLine
                    Text(L10n.FeatureOnboarding.or)
                        .typographyV1(.body3)
                    dividerLine
                }

                HStack {
                    SignInButton(isLoading: store.isWorking == .apple, icon: .Icon.apple) {
                        store.send(.signIn(.apple))
                    }
                    SignInButton(isLoading: store.isWorking == .google, icon: .Icon.google) {
                        store.send(.signIn(.google))
                    }
                    SignInButton(isLoading: store.isWorking == .microsoft, icon: .Icon.microsoft) {
                        store.send(.signIn(.microsoft))
                    }
                    SignInButton(isLoading: store.isWorking == .discord, icon: .Icon.discord) {
                        store.send(.signIn(.discord))
                    }
                }
            }
            .allowsHitTesting(store.isWorking == nil)

            Spacer()

            Text(attributedDisclosure)
                .typographyV1(.body2)
                .multilineTextAlignment(.center)
                .foregroundStyle(Color.SemanticV1.textPrimary)
                .tint(.SemanticV1.textLink)
                .padding(.top, 32)
        }
        .customBackButton(background: Material.ultraThin) { store.send(.cancelTapped) }
        .padding(24)
        .background(Color.SemanticV1.backgroundPrimary)
        .dynamicTypeSize((.small) ... .xxLarge)
        .onAppear { store.send(.onAppear) }
    }

    private var dividerLine: some View {
        Rectangle()
            .frame(height: 0.5)
            .foregroundStyle(Color.SemanticV1.borderPrimary)
    }

    var attributedDisclosure: AttributedString {
        let baseUrl = BackendEnvironmentProvider.currentConfiguration().webEndpointHost
        let baseString = L10n.FeatureOnboarding.signUpDisclaimer(baseUrl, baseUrl)
        var text = (try? AttributedString(markdown: baseString)) ?? AttributedString(baseString)
        let runsWithLinks = text.runs.filter { run in
            run.link != nil
        }

        for run in runsWithLinks {
            text[run.range].underlineStyle = .init(pattern: .solid, color: .SemanticV1.textLink)
        }
        return text
    }
}

#Preview {
    SignUpScreen(store: .init(initialState: .init(method: .login)) {
        SignUp()
    })
}
