import {Metrics as IOGatewayMetrics} from '@interopio/gateway-metrics-api';
import {Auth as IOGatewayAuth} from '@interopio/gateway-auth-api';
import {RestPublisherConfig} from './types/metrics/publisher/rest';
import {FilePublisherConfig} from './types/metrics/publisher/file';

export default IOGateway.Factory;

export declare namespace IOGateway {
    export const Factory: (options: GatewayConfig) => Gateway;

    export type AuthenticatorConfig = {
        timeout?: number
    }
    export type BasicAuthenticatorConfig = AuthenticatorConfig & {
        secretVerifier?: (username?: string, password?: string) => Promise<boolean>
    };

    export type Oauth2AuthenticatorConfig = AuthenticatorConfig & {
        /**
         * Base url, used to find the authorization server's app metadata.
         */
        issuerBaseURL?: string
        /**
         * Expected JWT "aud" claim value.
         */
        audience?: string
    }

    export import Auth = IOGatewayAuth;

    export type CustomAuthenticator = AuthenticatorConfig & {
        authenticator: (authentication: Auth.Request) => Promise<Auth.Response>
    }

    export type AuthenticationConfig = {
        /**
         * Default authentication provider if not specified by the client.
         */
        default?: string
        /**
         * List of authentication providers to be used by the gateway
         */
        available?: Exclude<string, 'default' | 'available'>[]
        basic?: BasicAuthenticatorConfig
        oauth2?: Oauth2AuthenticatorConfig
    } | Partial<Record<Exclude<string, 'default' |'available' | 'basic' | 'oauth2'>, CustomAuthenticator>>

    export type BasicMetricsPublisherConfig = {
        filters?: MetricFilters,
        heartbeats?: number
        buffer_size?: number
        conflation?: {
            'interval'?: number // default 1000
            'max-size'?: number // default 0
            'max-datapoints-repo'?: number
        }
    }
    export type FileMetricsPublisherConfig = BasicMetricsPublisherConfig & FilePublisherConfig;
    export type RestMetricsPublisherConfig = BasicMetricsPublisherConfig & RestPublisherConfig;
    export import Metrics = IOGatewayMetrics;

    export type CustomMetricsPublisherConfig = BasicMetricsPublisherConfig & {
        split_size?: number,
        publisher: {
            file: string,
            configuration: unknown;
        }
        context?: {
            gw?: { url: string }
            [key: string]: unknown
        }
    }

    export type MetricFilters = {
        publishers: {
            identity: Record<string, Filtering.Matcher>
            metrics: {
                allow?: Filtering.Matcher[]
                block?: Filtering.Matcher[]
                /**
                 * @deprecated
                 * @see allow
                 */
                whitelist?: Filtering.Matcher[]
                /**
                 * @deprecated
                 * @see block
                 */
                blacklist?: Filtering.Matcher[]
            }
        }[],
        non_matched?: Filtering.Action
    }

    export interface MetricPublishersConfig {
        file: FileMetricsPublisherConfig
        rest: RestMetricsPublisherConfig
    }

    export type MetricsConfig = {
        publishers: (keyof MetricPublishersConfig | CustomMetricsPublisherConfig)[]
        filters?: MetricFilters
    } & Partial<MetricPublishersConfig>

    export type MeshMember = Readonly<{endpoint: string, node: string }>;
    export type StaticMeshDirectoryConfig = {members: MeshMember[]};
    export type RestMeshDirectoryConfig = {uri?: string, interval?: number};

    /**
     * Gateway Mesh Configuration
     */
    export type MeshConfig = {
        /**
         * Node id to be used for mesh. If not specified fallbacks to GatewayConfig#node.
         */
        node?: string
        /**
         * @ignore
         */
        broker?: {
            endpoint: string
        },
        /**
         *
         */
        cluster?: {
            endpoint: string,
            relays?: string
            directory?: StaticMeshDirectoryConfig | RestMeshDirectoryConfig
        }
        websocket?: typeof WebSocket
    }

    export type VisibilityRule<K extends string> = {
        identity?: Record<string, Filtering.Matcher>
        restrictions: 'local' | 'cluster'
    } & (Partial<Record<K, Filtering.Matcher>>)

    export type GatewayConfig = {
        token?: {
            ttl?: number
            key?: string
        },
        authentication?: AuthenticationConfig
        metrics?: MetricsConfig
        node?: string,
        contexts?: {
            /**
             * Default context lifetime.
             * @experimental
             * @default 'ref-counted'
             */
            lifetime?: 'ref-counted' | 'ownership' | 'retained',
            /**
             * Default context visibility rules.
             */
            visibility?: VisibilityRule<'context'>[]
        },
        methods?: {
            /**
             * Default method visibility rules.
             */
            visibility?: VisibilityRule<'method'>[]
        },
        peers?: {
            /**
             * Default peer visibility.
             */
            visibility?: VisibilityRule<'domain'>[]
        }
        clients?: {
            inactive_seconds?: number,
            buffer_size?: number
        },
        /**
         * Mesh Configuration.
         */
        mesh?: MeshConfig
    }

    export interface Message<T = string> {
        type: T
    }

    export type GatewayClientOptions<T> = {
        key?: string,
        codec?: Encoding.Codec<Message, T>
        onPing?: (this: GatewayClient<T>) => void
        onDisconnect?: (this: GatewayClient<T>, reason: 'inactive' | 'shutdown') => void
    };

    export interface Gateway {
        start(environment?: { endpoint?: string }): Promise<Gateway>

        stop(): Promise<Gateway>

        info(): Record<string, unknown>

        client<T = Message>(handler: (this: GatewayClient<T>, msg: T) => void,
                            opts?: GatewayClientOptions<T>): GatewayClient<T>

        connect(handler: (client: GatewayClient, msg: Message) => void): Promise<GatewayClient>
    }

    export interface GatewayClient<T = Message> {
        send(msg: T): void

        close(): void
    }

    export namespace Filtering {
        export type ExactMatch = Omit<string, `#${string}`>;
        export type RegexMatch = RegExp | `#${string}` | `/${string}/`;
        export type Matcher = ExactMatch | RegExp;

        export type Action
            = 'allow' | 'block'
            // cultural sensitive deprecated values
            | 'whitelist' | 'blacklist';

        export function regexify(expression: ExactMatch | RegexMatch): Matcher;

        export function valueMatches(expression: Matcher, value?: unknown): boolean;

        export function valuesMatch(expressions: Matcher[], value?: string): boolean;
    }

    export namespace Logging {
        export interface Logger {
            trace(message?: string, ...args: unknown[]): void

            debug(message?: string, ...args: unknown[]): void

            info(message?: string, ...args: unknown[]): void

            warn(message?: string, ...args: unknown[]): void

            error(message?: string, ...args: unknown[]): void

            enabledFor(level: LogLevel): boolean;
        }

        export type LogLevel = Exclude<keyof Logger, 'enabledFor'>
        export type LogEvent = { time: Date, level: LogLevel, name: string, message: string, data: unknown[] }

        export type LogConfig = {
            level: LogLevel | { [key: string]: LogLevel }
            appender?: (event: LogEvent) => void
        }

        export function configure(config: LogConfig): void;

        export function getLogger(name: string): Logger;
    }

    export namespace Encoding {
        export type Codec<T, R> = {
            encode: (msg: T) => R;
            decode: (data: R) => T;
        };

        export type KeywordizeCommand
            = '*' // keywordize for all string values for ex {:type :cluster}
            | Set<string> // keywordize only specific values
            | null // stop keywordization (even for keys)
            ;

        export function transit<T>(opts?: {
            keywordize?: Map<string, KeywordizeCommand>;
            verbose?: boolean;
            namespaces?: Map<string, string>;
        }): Codec<T, string>;

        export function json<T>(): Codec<T, string>;

        export function direct<T>(opts?: {
            cljs?: boolean
        }): Codec<T, T>;
    }
}

