import type {
  IncomingMessage,
  ServerResponse as NodeServerResponse,
} from "node:http";
import type { Readable as NodeStream } from "node:stream";
import type { ReadableStream as NodeWebStream } from "node:stream/web";
import type {
  Async,
  PipelineHandler,
  SchemaOutput,
  StreamLike,
  TArray,
  TFile,
  TObject,
  TRecord,
  TStream,
  TString,
  TVoid,
} from "alepha";
import type { Route } from "alepha/router";
import type { RouteMethod } from "../constants/routeMethods.ts";
import type { ServerReply } from "../helpers/ServerReply.ts";
import type { UserAgentInfo } from "../services/UserAgentParser.ts";

export type TRequestBody = TObject | TString | TArray | TRecord | TStream;
export type TResponseBody =
  | TObject
  | TString
  | TRecord
  | TFile
  | TArray
  | TStream
  | TVoid;

export interface RequestConfigSchema {
  body?: TRequestBody;
  params?: TObject;
  query?: TObject;
  headers?: TObject;
  response?: TResponseBody;
}

export interface ServerRequestConfig<
  TConfig extends RequestConfigSchema = RequestConfigSchema,
> {
  body: SchemaOutput<TConfig["body"], any>;

  headers: SchemaOutput<TConfig["headers"], Record<string, string>>;

  params: SchemaOutput<TConfig["params"], Record<string, string>>;

  query: SchemaOutput<TConfig["query"], Record<string, any>>;
}

export type ServerRequestConfigEntry<
  TConfig extends RequestConfigSchema = RequestConfigSchema,
> = Partial<ServerRequestConfig<TConfig>>;

// ---------------------------------------------------------------------------------------------------------------------

export interface ServerRequest<
  TConfig extends RequestConfigSchema = RequestConfigSchema,
> extends ServerRequestConfig<TConfig> {
  /**
   * HTTP method used for this request.
   */
  method: RouteMethod;

  /**
   * Full request URL.
   */
  url: URL;

  /**
   * Unique request ID assigned to this request.
   */
  requestId: string;

  /**
   * Client IP address.
   * Uses `X-Forwarded-For` header when `TRUST_PROXY=true`.
   */
  ip?: string;

  /**
   * Value of the `Host` header sent by the client.
   */
  host?: string;

  /**
   * Browser user agent information.
   * Information are not guaranteed to be accurate. Use with caution.
   *
   * @see {@link UserAgentParser}
   */
  userAgent: UserAgentInfo;

  /**
   * Geolocation information derived from proxy headers.
   * Available when behind Cloudflare, Vercel, or similar CDNs.
   */
  geo: RequestGeo;

  /**
   * Whether the request appears to be from a bot/crawler.
   * Based on user-agent analysis.
   */
  isBot: boolean;

  /**
   * Whether the request is from a mobile device.
   * Based on user-agent analysis.
   */
  isMobile: boolean;

  /**
   * Request protocol (http or https).
   * Uses `X-Forwarded-Proto` header when behind a proxy.
   */
  protocol: "http" | "https";

  /**
   * Preferred language from `Accept-Language` header.
   * Returns the first/most preferred language code (e.g., "en", "fr", "en-US").
   */
  language?: string;

  /**
   * Parsed referer information.
   * Undefined if no Referer header or invalid URL.
   */
  referer?: RequestReferer;

  /**
   * Arbitrary metadata attached to the request. Can be used by middlewares to store information.
   */
  metadata: Record<string, any>;

  /**
   * Reply object to be used to send response.
   */
  reply: ServerReply;

  /**
   * The raw underlying request object (Web Request).
   */
  raw: ServerRawRequest;
}

// ---------------------------------------------------------------------------------------------------------------------

export interface ServerRoute<
  TConfig extends RequestConfigSchema = RequestConfigSchema,
> extends Route {
  /**
   * Handler function for this route.
   */
  handler: PipelineHandler;

  /**
   * HTTP method for this route.
   */
  method?: RouteMethod;

  /**
   * Request/response schema for this route.
   *
   * Request schema contains:
   * - body, for POST/PUT/PATCH requests
   * - params, for URL parameters (e.g. /user/:id)
   * - query, for URL query parameters (e.g. /user?id=123)
   * - headers, for HTTP headers
   *
   * Response schema contains:
   * - response
   *
   * Response schema is used to validate and serialize the response sent by the handler.
   */
  schema?: TConfig;

  /**
   * @see ServerLoggerProvider
   */
  silent?: boolean;

  /**
   * Mark this `GET` route as prerenderable. When `true`, the build invokes the
   * handler in-process and writes its body verbatim to `dist/public/{path}`,
   * so the route is served as a static file (e.g. `sitemap.xml`, `robots.txt`).
   * The route still serves live at request time for runtimes that have a server.
   */
  static?: boolean;
}

/**
 * Input type for `createRoute()`. Accepts both a `PipelineHandler` and a plain handler function.
 * Plain functions are auto-wrapped in `PipelineHandler` by `createRoute()`.
 */
export type ServerRouteInput<
  TConfig extends RequestConfigSchema = RequestConfigSchema,
> = Omit<ServerRoute<TConfig>, "handler"> & {
  handler: PipelineHandler | ServerHandler<TConfig>;
};

// ---------------------------------------------------------------------------------------------------------------------

export type ServerResponseBody<
  TConfig extends RequestConfigSchema = RequestConfigSchema,
> = SchemaOutput<TConfig["response"], ResponseBodyType>;

export type ResponseKind = "json" | "text" | "void" | "file" | "any";

export type ResponseBodyType =
  // not: object is not allowed, you want object ? add schema !
  // biome-ignore lint/suspicious/noConfusingVoidType: handlers may return void (no return statement)
  string | Buffer | StreamLike | undefined | null | void;

export type ServerHandler<
  TConfig extends RequestConfigSchema = RequestConfigSchema,
> = (request: ServerRequest<TConfig>) => Async<ServerResponseBody<TConfig>>;

export interface ServerResponse {
  body: string | Buffer | ArrayBuffer | NodeStream | NodeWebStream;
  headers: Record<string, string>;
  status: number;
}

export type ServerRouteRequestHandler = (
  request: ServerRequestData,
) => Promise<ServerResponse>;

export interface ServerRouteMatcher extends Route {
  handler: ServerRouteRequestHandler;
}

export interface ServerRequestData {
  readonly method: RouteMethod;
  readonly url: URL;
  readonly headers: Record<string, string>;
  readonly query: Record<string, string>;
  readonly params: Record<string, string>;
  raw: ServerRawRequest;
}

export interface ServerRawRequest {
  node?: NodeRequestEvent;
  web?: WebRequestEvent;
}

export interface NodeRequestEvent {
  req: IncomingMessage;
  res: NodeServerResponse;
}

export interface WebRequestEvent {
  req: Request;
  res?: Response;
}

// ---------------------------------------------------------------------------------------------------------------------

/**
 * Geolocation information from proxy headers (Cloudflare, Vercel, etc.)
 */
export interface RequestGeo {
  /**
   * ISO 3166-1 alpha-2 country code (e.g., "US", "FR", "JP").
   */
  country?: string;
  /**
   * City name (e.g., "San Francisco", "Paris").
   */
  city?: string;
  /**
   * Region/state (e.g., "California", "Île-de-France").
   */
  region?: string;
  /**
   * Latitude (if available).
   */
  latitude?: string;
  /**
   * Longitude (if available).
   */
  longitude?: string;
}

/**
 * Parsed referer information.
 */
export interface RequestReferer {
  /**
   * Full referer URL.
   */
  url: string;
  /**
   * Hostname of the referer (e.g., "google.com").
   */
  hostname: string;
  /**
   * Path of the referer URL.
   */
  pathname: string;
}
