import type {
    HttpInputMessage,
    HttpMethod,
    HttpOutputMessage,
    HttpRequest,
    HttpResponse,
    HttpStatusCode,
    MutableHttpHeaders,
    ReadonlyHttpHeaders,
    ResponseCookie
} from './http';
import type { WebSocketHandler } from './socket';
import type { AuthorizationRule, Principal } from '../auth';
import { PathContainer } from '../path';
import type { AsyncLocalStorage } from 'node:async_hooks';
import type { X509Certificate } from 'node:crypto';
import type { AddressInfo } from 'node:net';
import { IOGateway } from '@interopio/gateway';

export type OriginFilters = {
    non_matched?: IOGateway.Filtering.Action
    missing?: IOGateway.Filtering.Action
    block?: IOGateway.Filtering.Matcher[]
    allow?: IOGateway.Filtering.Matcher[]
    /**
     * @deprecated
     * @see block
     */
    blacklist?: IOGateway.Filtering.Matcher[]
    /**
     * @deprecated
     * @see allow
     */
    whitelist?: IOGateway.Filtering.Matcher[]
}

export type ServerHttpRequestMatcher = { method?: HttpMethod, path: IOGateway.Filtering.Matcher };

export type ServerCorsConfig = Readonly<{
    allowOrigin?: '*' | IOGateway.Filtering.Matcher[]
    allowHeaders?: string[]
    allowMethods?: HttpMethod[]
    exposeHeaders?: string[]
    allowCredentials?: boolean
    allowPrivateNetwork?: boolean
    maxAge?: number
}>;

export type ServerExchangeOptions = {
    authorize?: AuthorizationRule,
    cors?: boolean | ServerCorsConfig,
    origins?: OriginFilters,
}
export type ServerWebSocketOptions = ServerExchangeOptions & {
    ping?: number | { interval: number, data?: 'timestamp' | 'empty' },
    maxConnections?: number
}

export type ServerConfigurerHandlerSpec<T extends string = string> = {
    request: ServerHttpRequestMatcher, options?: ServerExchangeOptions
    handler: (exchange: ServerWebExchange, variables: { [key in T]: string }) => Promise<void>,
}

export type ServerWebSocketHandler = WebSocketHandler & {
    close?: () => Promise<void>
}

export type ServerConfigurerSocketSpec = {
    path?: string, options?: ServerWebSocketOptions
    factory: (server: { endpoint: string, storage?: AsyncLocalStorage<{ }> }) => Promise<ServerWebSocketHandler>,
}

export interface ServerConfigurer {
    handle(...handler: Array<ServerConfigurerHandlerSpec>): void;

    socket(...socket: Array<ServerConfigurerSocketSpec>): void;
}

export type Middleware<Request extends ServerHttpRequest = ServerHttpRequest, Response extends ServerHttpResponse = ServerHttpResponse> = ((context: ServerWebExchange<Request, Response>, next: () => Promise<void>) => Promise<void>)[];

export type ServerWebExchange<Request extends ServerHttpRequest = ServerHttpRequest, Response extends ServerHttpResponse = ServerHttpResponse> = {
    readonly request: Request
    readonly response: Response;
    attribute<T>(key: string): T | undefined;
    readonly attributes: Map<string, unknown>;
    principal<P extends Principal>(): Promise<P | undefined>;
    readonly logPrefix: string;
}

export interface ServerWebExchangeBuilder<Request extends ServerHttpRequest = ServerHttpRequest, Response extends ServerHttpResponse = ServerHttpResponse> {
    request(request: Request): this
    response(response: Response): this
    principal(principal: () => Promise<Principal>): this
    build(): ServerWebExchange<Request, Response>
}

/**
 * SSL/TLS connection information
 */
export type SslInfo = { peerCertificate?: X509Certificate };

export type ServerHttpRequest = HttpRequest<ReadonlyHttpHeaders> & HttpInputMessage<ReadonlyHttpHeaders> & {
    readonly id: string
    readonly path: string,
    readonly requestPath: RequestPath,
    // URL scheme without ':'
    readonly protocol: string
    /**
     * hostname[:port]
     */
    readonly host?: string

    formData(): Promise<URLSearchParams>
    text(): Promise<string>
    json(): Promise<unknown>

    readonly upgrade: boolean
    readonly remoteAddress?: AddressInfo
    /** SSL/TLS connection information (available for HTTPS requests) */
    readonly sslInfo?: SslInfo
}

export interface ServerHttpResponse extends HttpResponse<MutableHttpHeaders>, HttpOutputMessage<MutableHttpHeaders> {
    readonly statusCode: HttpStatusCode
    setStatusCode(statusCode: HttpStatusCode): boolean
    setRawStatusCode(statusCode?: number): boolean

    addCookie(cookie: ResponseCookie): this

}

export interface RequestPath extends PathContainer {
    readonly contextPath: PathContainer;
    readonly pathWithinApplication: PathContainer;
}
