import * as Sentry from '@sentry/ember';
import AuthConfiguration from 'ember-simple-auth/configuration';
import ESASessionService from 'ember-simple-auth/services/session';
import RSVP from 'rsvp';
import windowProxy from 'ghost-admin/utils/window-proxy';
import {getOwner} from '@ember/application';
import {inject} from 'ghost-admin/decorators/inject';
import {run} from '@ember/runloop';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';

export default class SessionService extends ESASessionService {
    @service configManager;
    @service('store') dataStore;
    @service feature;
    @service koenig;
    @service notifications;
    @service router;
    @service frontend;
    @service settings;
    @service ui;
    @service upgradeStatus;
    @service membersUtils;
    @service stateBridge;
    @service themeManagement;

    @inject config;

    @tracked user = null;

    skipAuthSuccessHandler = false;

    async populateUser(options = {}) {
        if (this.user) {
            return;
        }

        const id = options.id || 'me';
        const user = await this.dataStore.queryRecord('user', {id});
        this.user = user;
    }

    async postAuthPreparation() {
        await RSVP.all([
            this.configManager.fetchAuthenticated(),
            this.feature.fetch(),
            this.settings.fetch(),
            this.membersUtils.fetch()
        ]);

        // Theme management requires features to be loaded
        this.themeManagement.fetch().catch(console.error); // eslint-disable-line no-console

        await this.frontend.loginIfNeeded();

        // update Sentry with the full Ghost version which we only get after authentication
        if (this.config.sentry_dsn) {
            Sentry.configureScope((scope) => {
                scope.addEventProcessor((event) => {
                    return new Promise((resolve) => {
                        resolve({
                            ...event,
                            release: `ghost@${this.config.version}`,
                            user: {
                                role: this.user.role.name
                            }
                        });
                    });
                });
            });
        }

        this.loadServerNotifications();

        // pre-emptively load editor code in the background to avoid loading state when opening editor
        this.koenig.fetch();
    }

    // Some re-auth paths (`setup()` restoring a session, or `this.user` already
    // being populated) reach an authenticated state without running
    // `postAuthPreparation()`, leaving `config`/`settings` unloaded until a
    // refresh. Run it once if that happened.
    async ensurePostAuthPreparation(source) {
        if (this.configManager.isConfigLoaded) {
            return;
        }

        Sentry.captureMessage('Ran postAuthPreparation after authentication bypassed it', {
            tags: {source, ref: 'ONC-1774'}
        });

        try {
            await this.postAuthPreparation();
        } catch (err) {
            // continue the retry even if prep fails, as it did before this repair
        }
    }

    async handleAuthentication() {
        if (this.handleAuthenticationTask.isRunning) {
            return this.handleAuthenticationTask.last;
        }

        return this.handleAuthenticationTask.perform(() => {
            this.stateBridge.triggerEmberAuthChange();

            if (this.skipAuthSuccessHandler) {
                this.skipAuthSuccessHandler = false;
                return;
            }

            const redirectUrl = window.sessionStorage.getItem('ghost-signin-redirect');
            window.sessionStorage.removeItem('ghost-signin-redirect');
            if (redirectUrl && !redirectUrl.startsWith('/signin') && !redirectUrl.startsWith('/signup') && !redirectUrl.startsWith('/setup')) {
                // Hard navigate rather than router.transitionTo: the catch-all
                // react-fallback route has no controller-declared queryParams,
                // so transitionTo strips params like ?verifyEmail=<token> used
                // by newsletter reply-to confirmation links.
                windowProxy.replaceLocation(`${AuthConfiguration.rootURL}#${redirectUrl}`);
                return;
            }

            super.handleAuthentication('home');
        });
    }

    /**
     * Always try to re-setup session & retry the original transition
     * if user data is still available in session store although the
     * ember-session is unauthenticated.
     *
     * If success, it will retry the original transition.
     * If failed, it will be handled by the redirect to sign in.
     */
    async requireAuthentication(transition, route) {
        // Only when ember session invalidated
        if (!this.isAuthenticated) {
            transition.abort();

            if (this.user) {
                await this.setup();

                if (this.isAuthenticated) {
                    await this.ensurePostAuthPreparation('requireAuthentication');
                }

                this.notifications.clearAll();
                transition.retry();
            }
        }

        super.requireAuthentication(transition, route);
    }

    handleInvalidation() {
        let transition = this.appLoadTransition;

        if (transition) {
            transition.send('authorizationFailed');
        } else {
            run.scheduleOnce('routerTransitions', this, 'triggerAuthorizationFailed');
        }
    }

    // TODO: this feels hacky, find a better way than using .send
    triggerAuthorizationFailed() {
        getOwner(this).lookup(`route:${this.router.currentRouteName}`)?.send('authorizationFailed');
    }

    loadServerNotifications() {
        if (this.isAuthenticated) {
            if (!this.user.isAuthorOrContributor) {
                this.dataStore.findAll('notification', {reload: true}).then((serverNotifications) => {
                    serverNotifications.forEach((notification) => {
                        if (notification.top || notification.custom) {
                            this.notifications.handleNotification(notification);
                        } else {
                            this.upgradeStatus.handleUpgradeNotification(notification);
                        }
                    });
                });
            }
        }
    }

    @task({drop: true})
    *handleAuthenticationTask(callback) {
        if (!this.user) {
            try {
                yield this.populateUser();
            } catch (err) {
                yield this.invalidate();
            }

            yield this.postAuthPreparation();
        } else {
            yield this.ensurePostAuthPreparation('handleAuthentication');
        }

        callback();
    }
}
