import {
    HeaderValue,
    HeaderValues,
    HttpRequest, HttpResponse,
    MutableHttpHeaders,
    ReadonlyHttpHeaders,
    ServerHttpRequest,
    ServerHttpResponse,
    WebExchange
} from './types.js';
import http from 'node:http';
import http2 from 'node:http2';

function requestToProtocol(request: ServerHttpRequest, defaultProtocol: string): string {
    let proto = request.headers.get('x-forwarded-proto');

    if (Array.isArray(proto)) {
        proto = proto[0];
    }
    if (proto !== undefined) {
        return (proto as string).split(',', 1)[0].trim();
    }
    return defaultProtocol;
}

function requestToHost(request: ServerHttpRequest, defaultHost?: string): string | undefined {
    let host = request.headers.get('x-forwarded-for');
    if (host === undefined) {
        host = request.headers.get('x-forwarded-host');
        if (Array.isArray(host)) {
            host = host[0];
        }
        if (host) {
            const port = request.headers.one('x-forwarded-port');
            if (port) {
                host = `${host}:${port}`;
            }
        }
        if (host === undefined) {
            host = request.headers.one('host');
        }
    }
    if (Array.isArray(host)) {
        host = host[0];
    }
    if (host) {
        return (host as string).split(',', 1)[0].trim();
    }



    return defaultHost;
}

export class HttpServerRequest implements ServerHttpRequest {
    private _body: Promise<Blob> | undefined;
    private _url: URL | undefined;
    private readonly _headers: ReadonlyHttpHeaders;
    constructor(readonly _req: http.IncomingMessage) {
        this._headers = new IncomingMessageHeaders(_req);
    }

    get http2(): boolean {
        return this._req.httpVersionMajor >= 2;
    }

    get headers() {
        return this._headers;
    }

    get path() {
        return this.URL?.pathname;
    }

    get URL() {
        this._url ??= new URL(this._req.url!, `${this.protocol}://${this.host}`);
        return this._url;
    }
    get query() {
        return this.URL?.search;
    }

    get method() {
        return this._req.method;
    }

    get host() {
        let dh: string | undefined = undefined;
        if (this._req.httpVersionMajor >= 2) {
            dh = (this._req?.headers as http2.IncomingHttpHeaders)[':authority'];
        }
        if (dh === undefined) {
            dh = this._req?.socket.remoteAddress;
        }
        return requestToHost(this, dh);
    }

    get protocol() {
        let dp: string | undefined = undefined;
        if (this._req.httpVersionMajor > 2) {
            dp = (this._req.headers as http2.IncomingHttpHeaders)[':scheme'];
        }
        if (dp === undefined) {
            dp = this._req?.socket['encrypted'] ? 'https' : 'http'
        }
        return requestToProtocol(this, dp);
    }

    get socket() {
        return this._req.socket;
    }

    get body() {
        this._body ??= new Promise((resolve, reject) => {
            const chunks: Uint8Array[] = [];
            this._req
                .on('error', (err: Error) => reject(err))
                .on('data', (chunk) => chunks.push(chunk))
                .on('end', () => {
                    resolve(new Blob(chunks));
                });
        });
        return this._body;
    }

    get text() {
        return this.body.then(async (blob) => await blob.text());
    }

    get json() {
        return this.body.then(async (blob) => {
            const json = JSON.parse(await blob.text());
            return json;
            // try {
            // } catch (e) {
            //     reject(e instanceof Error ? e : new Error(`parse failed :${e}`));
            // }
        });
    }
}

class IncomingMessageHeaders implements ReadonlyHttpHeaders {
    constructor(private readonly _msg: http.IncomingMessage) {
    }

    has(name: string): boolean {
        return this._msg.headers[name] !== undefined;
    }

    get(name: string): string | (readonly string[]) | undefined {
        return this._msg.headers[name];
    }

    list(name: string): string[] {
        return toList(this._msg.headers[name]);
    }

    one(name: string): string | undefined {
        const value = this._msg.headers[name];
        if (Array.isArray(value)) {
            return value[0];
        }
        return value;
    }
    keys() {
        return Object.keys(this._msg.headers).values();
    }
}

class OutgoingMessageHeaders implements MutableHttpHeaders {
    constructor(private readonly _msg: http.OutgoingMessage) {
    }

    has(name: string): boolean {
        return this._msg.hasHeader(name);
    }

    keys() {
        return this._msg.getHeaderNames().values();
    }

    get(name: string): HeaderValues {
        return this._msg.getHeader(name);
    }

    one(name: string): HeaderValue {
        const value = this._msg.getHeader(name);
        if (Array.isArray(value)) {
            return value[0];
        }
        return value;
    }

    set(name: string, value: HeaderValues): this {
        if (!this._msg.headersSent) {
            if (Array.isArray(value)) {
                value = value.map(v => typeof v === 'number' ? String(v) : v);
            } else if (typeof value === 'number') {
                value = String(value);
            }

            if (value) {
                this._msg.setHeader(name, value);
            }
            else {
                this._msg.removeHeader(name);
            }
        }
        return this;
    }

    add(name: string, value: string | (readonly string[])): this {
        if (!this._msg.headersSent) {
            this._msg.appendHeader(name, value);
        }
        return this;
    }

    list(name: string): string[] {
        const values = this.get(name);
        return toList(values);
    }
}

export class HttpServerResponse implements ServerHttpResponse {
    private readonly _headers: MutableHttpHeaders;
    constructor(readonly _res: http.ServerResponse) {
        this._headers = new OutgoingMessageHeaders(_res);
    }

    get statusCode(): number {
        return this._res.statusCode;
    }

    set statusCode(value: number) {
        if (this._res.headersSent) {
            return;
        }
        this._res.statusCode = value;
    }

    get headers() {
        return this._headers;
    }
}

export class DefaultWebExchange<Request extends ServerHttpRequest, Response extends ServerHttpResponse> extends WebExchange<Request, Response> {
    constructor(readonly request: Request, readonly response: Response) {
        super();
    }
}

function toList(values: number | string | (readonly string[]) | undefined): string[] {
    if (typeof values === 'string') {
        values = [values];
    }
    if (typeof values === 'number') {
        values = [String(values)];
    }
    const list: string[] = [];
    if (values) {
        for (const value of values) {
            if (value) {
                list.push(...parseHeader(value));
            }
        }
    }
    return list;
}

function parseHeader(value: string): string[] {
    const list: string[] = [];
    {
        let start = 0;
        let end = 0;

        for (let i = 0; i < value.length; i++) {
            switch (value.charCodeAt(i)) {
                case 0x20: // space
                    if (start === end) {
                        start = end = i + 1;
                    }
                    break;
                case 0x2c: // comma
                    list.push(value.slice(start, end));
                    start = end = i + 1;
                    break;
                default:
                    end = end + 1;
                    break;
            }
        }
        list.push(value.slice(start, end));
    }

    return list;
}

export class MapHttpHeaders extends Map<string, (readonly string[])> implements MutableHttpHeaders {

    get(name: string) {
        return super.get(name.toLowerCase());
    }

    one(name: string): string | undefined {
        return this.get(name)?.[0];
    }

    list(name: string) {
        const values = super.get(name.toLowerCase());
        return toList(values);
    }

    set(name: string, value: number | string | (readonly string[]) | undefined): this {
        if (typeof value === 'number') {
            value = String(value);
        }
        if (typeof value === 'string') {
            value = [value];
        }
        if (value) {
            return super.set(name.toLowerCase(), value);
        }
        else {
            super.delete(name.toLowerCase());
            return this;
        }
    }

    add(name: string, value: string | (readonly string[])) {
        const prev = super.get(name.toLowerCase());
        if (typeof value === 'string') {
            value = [value];
        }
        if (prev) {
            value = prev.concat(value);
        }
        this.set(name, value);
        return this;
    }
}

export class MockHttpRequest implements HttpRequest<MutableHttpHeaders> {
    private _body?: Blob;
    readonly headers = new MapHttpHeaders();

    constructor(private readonly url: URL, method?: string) {
        this.method = method ?? 'GET';
        this.headers.set('Host', url.hostname);
        this.path = url?.pathname ?? '/';
    }
    method: string;
    path: string;

    get host() {
        return requestToHost(this, this.url.host);
    }
    get protocol() {
        return requestToProtocol(this, this.url.protocol.slice(0, -1));
    }

    get body(): Promise<Blob> {
        const body = this._body;
        return body ? Promise.resolve(body): Promise.reject(new Error(`no body set`));
    }
    set body(value: Blob) {
        this._body = value;
    }

    get URL() {
        return new URL(this.path, `${this.protocol}://${this.host}`);
    }
}

export class MockHttpResponse implements HttpResponse<MutableHttpHeaders> {
    statusCode!: number;
    headers = new MapHttpHeaders();

}
