import Axios, { AxiosInstance, AxiosRequestConfig } from "axios";
const rateLimit = require("axios-rate-limit");
import { parseCookies } from "nookies";
import "reflect-metadata";
import { getUserTokens } from "./browser_auth/auth";
import { Config } from "./Config";
import { Content } from "./resources/Content";
import { Contribute } from "./resources/Contribute";
import { Search } from "./resources/Search";
import { API_Config } from "./Types";

export interface IClientArgs {
    org: string;
    token: string;
    rootMapId: string;
    timeout?: number;
    scheme?: string;
    hostname?: string;
    env?: string;
    content_root?: string;
    loginRedirect?: string;
    loginRequired?: boolean;
    interceptors?: Array<(axios: AxiosInstance) => void>;
    rateLimit?: {
        maxRequests?: number;
        maxRPS: number;
    }
}

const EZD_CLIENT_VERSION = "0.1.0";
const API_VERSION = "v1";

export default class Client {
    public readonly base_url: string;
    public content_axios: AxiosInstance;
    public contribute_axios: AxiosInstance;
    public content: Content;
    public search: Search;
    public contribute: Contribute;
    public content_service_token: string;
    public initial_args: IClientArgs;

    constructor(args: IClientArgs) {
        const {
            org,
            token,
            rootMapId,
            timeout = 3000,
            scheme = "https://",
            hostname = "content.easydita.com",
            env = "latest",
            content_root= "content",
            interceptors = [],
            rateLimit: rateLimitConfig = {
                maxRPS: 5,
                maxRequests: 2
            }
        } = args;
        this.initial_args = args;

        if (!org) {
            throw new Error("easyDITA org not set");
        }
        if (!token) {
            throw new Error("easyDITA token not set");
        }
        if (!rootMapId) {
            throw new Error("easyDITA rootMapId not set");
        }

        this.base_url = `${scheme}${hostname}/org/${org}/env/${env}/map/${rootMapId}/`;
        this.content_service_token = token;

        const config: AxiosRequestConfig = {
            baseURL: this.base_url,
            timeout,
            // withCredentials: true,
            headers: {
                // "X-EZD-Client": "JS/" + EZD_CLIENT_VERSION,
            },
        };

        this.content_axios = rateLimit(Axios.create(config), rateLimitConfig);
        this.content_axios.interceptors.request.use(intercepted_config => {
            if (!intercepted_config.params) {
                intercepted_config.params = {};
            }

            // intercepted_config.headers["Content-Type"] = "application/text";
            intercepted_config.params.token = this.user_token() ? this.user_token() : this.content_service_token;
            intercepted_config.params.link_root = content_root;
            return intercepted_config;
        });

        interceptors.forEach(interceptor => interceptor(this.content_axios));

        this.contribute_axios = Axios.create(config);
        this.contribute_axios.interceptors.request.use(intercepted_config => {
            if (!intercepted_config.params) {
                intercepted_config.params = {};
            }

            intercepted_config.params.rootMapId = rootMapId;
            intercepted_config.params.token = this.user_token();
            return intercepted_config;
        });

        const portal_config = new Config(this.content_axios);

        this.content = new Content({ axios: this.content_axios, config: portal_config });
        this.search = new Search({ axios: this.content_axios, rootMapId, config: portal_config });
        this.contribute = new Contribute({ axios: this.contribute_axios, config: portal_config });
    }

    public iframe_url() {
        let s = this.base_url;
        s = s.replace("https://", "");
        s = s.replace("http://", "");
        return s;
    }

    public user_token() {
        if (typeof window !== "undefined") {
            return getUserTokens().idToken;
        } else {
            return null;
        }
    }

    public async login() {
        // create an ezd session so the editor will load without asking user to login first
        const response = await this.content_axios.post("/auth");
    }

    public async prepare(args): Promise<Client> {
        const redirect_path = args.req ?
            `${args.req.protocol}://${args.req.get("host")}${args.req.path}` :
            `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
        const cookies = args.req ? args.req.cookies : parseCookies();
        const token = (args.req && args.req.query.portal_auth_token) || (cookies && cookies.portal_auth_token); // Make sure to keep this order -- query param overrides cookie

        const requireLogin = () => {
            const destination = `${this.initial_args.loginRedirect}?redirect_to=${encodeURI(redirect_path)}`;
            if (args.res) {
                return args.res.redirect(destination);
            } else {
                window.location.href = destination;
                return;
            }
        };

        if (!token) {
            if (this.initial_args.loginRequired) {
                return requireLogin();
            } else {
                return this;
            }
        }

        try {
            const client = new Client({
                ...this.initial_args,
                token,
            });

            await client.content.getSections();

            if (args.req && args.req.query.portal_auth_token) {
                args.res.cookie("portal_auth_token", args.req.query.portal_auth_token, {path: "/", domain: args.req.hostname});
            }
            return client;

        } catch (e) {
            if (e.response && e.response.status === 403) {
                if (args.res) {
                    args.res.clearCookie("portal_auth_token");
                }
                if (this.initial_args.loginRedirect) {
                    return requireLogin();
                }
                return new Client(this.initial_args);
            } else {
                throw e;
            }
        }
    }

}
