import dayjs from 'dayjs';
import jsCookie from 'js-cookie';
import jwtDecode from 'jwt-decode';
import lodash from 'lodash';
import { BindAll } from 'lodash-decorators';
import { action, computed, observable } from 'mobx';
import { persist } from 'mobx-persist';
import { lastValueFrom, Subject } from 'rxjs';
import { AuthOptions } from './options';
export interface IAuthOptions {
    /**
     * 持久化 Key
     * @type {Array<string>}
     * @memberof IAuthOptions
     */
    StorageKey?: string
    /**
    * Cookie Key
    * @type {Array<string>}
    * @memberof IAuthOptions
    */
    CookieKey?: Array<string>
    /**
     * Cookie 域名
     * @type {Array<string>}
     * @memberof IAuthOptions
     */
    CookieDomain?: Array<string>
    /**
     * Token 类型 
     * @type {('JWT' | 'Other')}
     * @memberof IPortalAuthOptions
     */
    TokenType?: 'JWT' | 'Other'
}
@BindAll()
export class AuthController {
    constructor(options: IAuthOptions = {}) {
        this.resetConfig(options)
        this.createHydrate()
    }
    /**
    * 持久化初始化完成 Subject
    * @type {Promise<any>}
    * @memberof ControllerUser
    */
    protected readonly HydrateSubject = new Subject<Boolean>();
    /**
    * 持久化初始化完成 Promise
    * @type {Promise<any>}
    * @memberof ControllerUser
    */
    get HydrateAsync(): Promise<this> {
        return lastValueFrom(this.HydrateSubject, { defaultValue: undefined });
    }
    /**
    * 异步 HydrateSubject 已经完成
    * @readonly
    * @memberof PortalAuthController
    */
    get HydrateisStopped() {
        return this.HydrateSubject.isStopped
    }
    readonly options: IAuthOptions = {
        StorageKey: 'mamba-auth',
        CookieKey: ['mamba-auth'],
        CookieDomain: ['.lenovo.com.cn', '.lenovo.com'],
        TokenType: 'JWT'
    }
    get JsCookie() {
        return jsCookie
    }
    get JWTDecode() {
        return jwtDecode
    }
    get IsJWT() {
        return lodash.eq(this.options.TokenType, 'JWT')
    }
    /**
     * 存储 key
     * @readonly
     * @memberof AuthController
     */
    get StorageKey() {
        return `_Auth_${this.options.StorageKey}`
    }
    /**
     * CookieKey
     * @readonly
     * @memberof AuthController
     */
    get CookieKey() {
        if (AuthOptions.browser) {
            return lodash.concat([], this.options.CookieKey)
        }
        return []
    }
    /**
     * Cookie 站点信息
     * @readonly
     * @memberof AuthController
     */
    get CookieDomain() {
        if (AuthOptions.browser) {
            return lodash.concat([`.${window.location.hostname}`], this.options.CookieDomain)
        }
        return []
    }
    /**
     * 所有的 Cookie
     * @readonly
     * @memberof AuthController
     */
    get AllCookieKeys() {
        if (AuthOptions.browser) {
            try {
                const aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
                for (let nIdx = 0; nIdx < aKeys.length; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
                return aKeys;
            } catch (error) {
                return []
            }
        }
    }
    /**
    * 重置配置
    * @param options 
    */
    resetConfig(options: IAuthOptions = {}) {
        try {
            AuthOptions.writeCheck()
            lodash.merge(this.options, lodash.pick(options, ['StorageKey', 'CookieKey', 'CookieDomain']))
        } catch (error) {
            AuthOptions.log('error', error)
        }
    }
    /**
     * AccessToken
     * @protected
     * @memberof AuthController
     */
    @observable protected _AccessToken = undefined;
    /**
     * 最后一次更新 值 HydrateisStopped 未完成前 存储 用于对比变化 default 表示默认无值
     * @protected
     * @memberof AuthController
     */
    protected lastValue = 'default';
    /**
     * 提供外部访问的 AccessToken
     * @readonly
     * @memberof AuthController
     */
    @computed
    get AccessToken() {
        const AccessToken = this.getAccessToken()
        const Decoded = this.getDecoded(AccessToken);
        if (Decoded && Decoded.overdue) {
            AuthOptions.log('Warning 已过期 不返回', Decoded, this)
            return undefined
        }
        return AccessToken
    }
    /**
     * 解析后的 JWT
     * @readonly
     * @type {JWTDecoded}
     * @memberof AuthController
     */
    @computed
    get JwtDecoded() {
        return this.getDecoded()
    }
    /**
     * Cookie 中存储的值 
     * @readonly
     * @private
     * @memberof AuthController
     */
    private get CookieAccessToken() {
        // 微信小程序中不使用 cookie
        if (AuthOptions.isWeappBowser) {
            return undefined
        }
        if (AuthOptions.browser) {
            try {
                return lodash.head(lodash.compact(lodash.map(this.CookieKey, key => jsCookie.get(key))))
            } catch (error) {
                AuthOptions.log('error', this, error)
                return undefined
            }
        }
    }
    /**
     * 获取 JWT 解析后数据
     * @returns 
     */
    getDecoded(AccessToken = this.getAccessToken()): JWTDecoded {
        try {
            if (!AccessToken || !this.IsJWT) {
                return {
                } as JWTDecoded
            }
            const Decode: JWTDecoded = jwtDecode(AccessToken);
            return lodash.assign<JWTDecoded, Partial<JWTDecoded>>(Decode, {
                expFormat: dayjs(Decode.exp * 1000).format('YYYY-MM-DD HH:mm:ss'),
                iatFormat: dayjs(Decode.iat * 1000).format('YYYY-MM-DD HH:mm:ss'),
                // 是否过期
                overdue: dayjs(Decode.exp * 1000).isBefore(dayjs())
            })
        } catch (error) {
            // AuthOptions.log('error', 'Decoded', this, error)
            return {
                overdue: true
            } as JWTDecoded
        }
    }
    /**
     * 获取 AccessToken
     * @returns 
     */
    getAccessToken() {
        return lodash.head(lodash.compact([this.CookieAccessToken, this._AccessToken]))
    }
    /**
     * 保存 Token
     * @param _AccessToken 
     * @returns 
     */
    @action
    onSaveAccessToken(_AccessToken: string = undefined, setCookie = false) {
        try {
            AuthOptions.writeCheck();
            AuthOptions.trace('Save AccessToken', _AccessToken, setCookie)
            if (_AccessToken && this.IsJWT) {
                const Decoded = this.getDecoded(_AccessToken);
                AuthOptions.log('AccessToken Decoded', Decoded)
                // if (!Decoded.aud) {
                //     throw 'AccessToken 非法'
                // }
                if (Decoded.overdue) {
                    throw 'AccessToken 已过期'
                }
            }
            if (_AccessToken && lodash.eq(this.getAccessToken(), _AccessToken)) {
                throw 'AccessToken 已存在 【 Cookie 存在将不在写入 】'
            }
            if (!this.HydrateisStopped) {
                this.lastValue = _AccessToken;
            }
            this._AccessToken = _AccessToken;
            if (setCookie && AuthOptions.browser) {
                lodash.map(this.CookieDomain, domain => {
                    lodash.map(this.CookieKey, key => {
                        AuthOptions.log('jsCookie set', key, _AccessToken, domain)
                        jsCookie.set(key, _AccessToken)
                        jsCookie.set(key, _AccessToken, { domain })
                    })
                })
            }
        } catch (error) {
            AuthOptions.log('AccessToken Error', error, this)
        }
    }
    /**
     * 清理所有的登录信息
     * @return {*} 
     * @memberof AuthController
     */
    @action
    onClear() {
        try {
            AuthOptions.writeCheck()
            this._AccessToken = undefined;
            if (!this.HydrateisStopped) {
                this.lastValue = undefined;
            }
            this.onRemove()
            lodash.map(this.CookieDomain, domain => this.onRemove({ domain }))
            // return AuthOptions.LocalForage.clear();
        } catch (error) {
            AuthOptions.log('error', error)
        }
    }

    private onRemove(options?) {
        if (AuthOptions.browser) {
            try {
                AuthOptions.writeCheck()
                lodash.map(this.CookieKey, key => jsCookie.remove(key, options))
                lodash.map(this.AllCookieKeys, key => jsCookie.remove(key, options))
            } catch (error) {
                AuthOptions.log('error', error)
            }
        }
    }
    /**
    * 创建持久化存储
    * @memberof BaseModel
    */
    protected async createHydrate() {
        try {
            if (!AuthOptions.browser) {
                throw '非浏览器环境 不进行 Hydrate'
            }
            // 微信小程序中不使用 cookie
            if (AuthOptions.isWeappBowser) {
                this.onRemove()
                lodash.map(this.CookieDomain, domain => this.onRemove({ domain }))
            }
            const Hydrate = AuthOptions.createHydrate()
            persist({
                _AccessToken: true
            })(this);
            AuthOptions.log(`Storage ${this.StorageKey}`, this)
            await Hydrate(this.StorageKey, this);
            if (!lodash.eq(this.lastValue, 'default') && !lodash.isEqual(this._AccessToken, this.lastValue)) {
                AuthOptions.log(`Storage ${this.StorageKey} LastValue`, this, this.lastValue)
                this.onSaveAccessToken(this.lastValue);
            }
            // jwt 校验正确性
            if (this.IsJWT) {
                const Decoded = this.getDecoded();
                // 过期清理 或者无效
                if (Decoded.overdue) {
                    this.onClear()
                }
            }
            // const CookieAccessToken = lodash.find(this.CookieAccessToken, lodash.identity);
            // if (!this._AccessToken && CookieAccessToken) {
            //     this.onSaveAccessToken(CookieAccessToken)
            // }
            this.HydrateSubject.next(true)
            this.HydrateSubject.complete()
        } catch (error) {
            if (AuthOptions.browser) {
                AuthOptions.log('error', error)
            }
            this.HydrateSubject.next(false)
            this.HydrateSubject.complete()
        }
    }
}
/**
 * @docs https://www.jianshu.com/p/d1644e281250
 */
export interface JWTDecoded {
    /** @desc 用户 */
    aud: string;
    /** @desc 主题 */
    sub: string;
    /** @desc 身份 */
    identityId: string;
    /** @desc 发行人 */
    iss: string;
    /** @desc 过期时间 */
    exp: number;
    expFormat: string;
    /** @desc 颁发时间 */
    iat: number;
    iatFormat: string;
    /** @desc 用户名 */
    username: string;
    /** @desc 过期 */
    overdue: boolean;
}