import {
  useSession as useAthenaSession,
  type UseSessionOptions,
  type UseSessionResult,
} from "@xylex-group/athena/react";
import type {
  AthenaAuthErrorDetails,
  AthenaAuthSessionResponse,
} from "@xylex-group/athena";

import { getAuthApiOrigin } from "./env";
import { callAuthEndpoint, type CallAuthEndpointOptions } from "./http";
import { normalizeTenantSelection, type TenantSelection } from "./tenant";

export type DemoAuthResult<T> = {
  ok: boolean;
  status: number;
  data: T | null;
  error: string | null;
  errorDetails?: AthenaAuthErrorDetails | null;
  raw: unknown;
};

export type DemoAuthSessionResponse = AthenaAuthSessionResponse;

export type DemoAuthRequestInput = {
  endpoint: string;
  method?: "GET" | "POST";
  body?: Record<string, unknown> | string;
  query?: Record<string, string | number | boolean | Array<string | number | boolean>>;
  fetchOptions?: RequestInit;
};

type SocialSignInResponse = {
  url?: string;
  redirect?: boolean;
  [key: string]: unknown;
};

export type DemoOAuthClientRecord = {
  id: string;
  clientName: string;
  provider: string;
  description?: string | null;
  responseType: string;
  grantType: string;
  tokenAuthenticationMethod: string;
  redirectUris: string[];
  clientUrl?: string | null;
  scopes: string[];
  isActive: boolean;
  metadata: Record<string, unknown>;
  createdAt: string;
  updatedAt: string;
};

export type DemoCreateOAuthClientInput = {
  clientName: string;
  provider: string;
  redirectUris: string[];
  description?: string;
  responseType?: string;
  grantType?: string;
  tokenAuthenticationMethod?: string;
  clientUrl?: string;
  scopes?: string[];
  isActive?: boolean;
  metadata?: Record<string, unknown>;
};

type DemoOAuthClientListResponse = {
  oauthClients: DemoOAuthClientRecord[];
};

export type DemoAuthClient = {
  signIn: {
    email: (input: Record<string, unknown>) => Promise<DemoAuthResult<DemoAuthSessionResponse>>;
    username: (input: Record<string, unknown>) => Promise<DemoAuthResult<DemoAuthSessionResponse>>;
    social: (input: Record<string, unknown>) => Promise<DemoAuthResult<SocialSignInResponse>>;
  };
  signUp: {
    email: (input: Record<string, unknown>) => Promise<DemoAuthResult<DemoAuthSessionResponse>>;
  };
  signOut: (input?: Record<string, unknown>) => Promise<DemoAuthResult<unknown>>;
  sendVerificationEmail: (input: Record<string, unknown>) => Promise<DemoAuthResult<unknown>>;
  forgetPassword: (input: Record<string, unknown>) => Promise<DemoAuthResult<unknown>>;
  requestPasswordReset: (input: Record<string, unknown>) => Promise<DemoAuthResult<unknown>>;
  admin: {
    oauthClient: {
      create: (
        input: DemoCreateOAuthClientInput,
      ) => Promise<DemoAuthResult<DemoOAuthClientRecord>>;
      list: () => Promise<DemoAuthResult<DemoOAuthClientListResponse>>;
    };
  };
  getSession: () => Promise<DemoAuthResult<DemoAuthSessionResponse>>;
  request: <T = unknown>(input: DemoAuthRequestInput) => Promise<DemoAuthResult<T>>;
  useSession: (
    options?: UseSessionOptions,
  ) => UseSessionResult<DemoAuthSessionResponse>;
};

export type DemoAuthClientConfig = {
  authApiOrigin?: string;
  authBasePath?: string;
  tenantId?: string;
  tenantHeaderName?: string;
  requestDefaults?: Omit<RequestInit, "method" | "body">;
};

function normalizeRequestTenant(input?: DemoAuthClientConfig): TenantSelection | undefined {
  if (!input?.tenantId && !input?.tenantHeaderName) return;
  return normalizeTenantSelection({
    tenantId: input?.tenantId,
    headerName: input?.tenantHeaderName,
  });
}

function normalizeAuthClientConfig(
  configOrBaseURL?: string | DemoAuthClientConfig,
): DemoAuthClientConfig {
  if (typeof configOrBaseURL === "string") {
    return { authApiOrigin: configOrBaseURL };
  }
  return configOrBaseURL ?? {};
}

function buildQueryString(
  query: DemoAuthRequestInput["query"] | undefined,
): string {
  if (!query) return "";

  const params = new URLSearchParams();
  for (const [key, value] of Object.entries(query)) {
    if (Array.isArray(value)) {
      for (const entry of value) {
        params.append(key, String(entry));
      }
      continue;
    }
    params.append(key, String(value));
  }

  const serialized = params.toString();
  return serialized ? `?${serialized}` : "";
}

function toRequestBody(
  body: DemoAuthRequestInput["body"] | undefined,
): string | undefined {
  if (body == null) return undefined;
  if (typeof body === "string") return body;
  return JSON.stringify(body);
}

function toDemoAuthResult<T>(
  result: {
    ok: boolean;
    status: number;
    body: unknown;
    message: string;
  },
): DemoAuthResult<T> {
  const body = result.body;
  const bodyRecord = body && typeof body === "object" ? (body as Record<string, unknown>) : null;
  const hasWrappedData = Boolean(bodyRecord && "data" in bodyRecord);
  const data = hasWrappedData
    ? ((bodyRecord?.data as T | null) ?? null)
    : result.ok
      ? (body as T)
      : null;
  const error = hasWrappedData
    ? (typeof bodyRecord?.error === "string" ? bodyRecord.error : null)
    : result.ok
      ? null
      : result.message;

  return {
    ok: result.ok,
    status: result.status,
    data: data ?? null,
    error,
    errorDetails:
      bodyRecord && "errorDetails" in bodyRecord
        ? (bodyRecord.errorDetails as AthenaAuthErrorDetails | null)
        : null,
    raw: body,
  };
}

export function createAuthClient(
  configOrBaseURL?: string | DemoAuthClientConfig,
): DemoAuthClient {
  const config = normalizeAuthClientConfig(configOrBaseURL);
  const resolvedTenant = normalizeRequestTenant(config);
  const requestOptions: CallAuthEndpointOptions = {
    baseURL: (config.authApiOrigin ?? getAuthApiOrigin()).replace(/\/$/, ""),
    basePath: config.authBasePath,
    tenant: resolvedTenant,
    requestDefaults: config.requestDefaults,
  };

  const request = async <T = unknown>(
    input: DemoAuthRequestInput,
  ): Promise<DemoAuthResult<T>> => {
    const endpoint = input.endpoint.startsWith("/") ? input.endpoint : `/${input.endpoint}`;
    const path = `${endpoint}${buildQueryString(input.query)}`;
    const method = input.method ?? (input.body == null ? "GET" : "POST");
    const result = await callAuthEndpoint<unknown>(
      path,
      {
        ...input.fetchOptions,
        method,
        body: method === "POST" ? toRequestBody(input.body) : undefined,
      },
      requestOptions,
    );
    return toDemoAuthResult<T>(result);
  };

  const getSession = () =>
    request<DemoAuthSessionResponse>({
      endpoint: "/get-session",
      method: "GET",
    });

  const forgetPassword = (input: Record<string, unknown>) =>
    request<unknown>({
      endpoint: "/forget-password",
      method: "POST",
      body: input,
    });

  const client: DemoAuthClient = {
    signIn: {
      email: (input: Record<string, unknown>) =>
        request<DemoAuthSessionResponse>({
          endpoint: "/sign-in/email",
          method: "POST",
          body: input,
        }),
      username: (input: Record<string, unknown>) =>
        request<DemoAuthSessionResponse>({
          endpoint: "/sign-in/username",
          method: "POST",
          body: input,
        }),
      social: (input: Record<string, unknown>) =>
        request<SocialSignInResponse>({
          endpoint: "/sign-in/social",
          method: "POST",
          body: input,
        }),
    },
    signUp: {
      email: (input: Record<string, unknown>) =>
        request<DemoAuthSessionResponse>({
          endpoint: "/sign-up/email",
          method: "POST",
          body: input,
        }),
    },
    signOut: (input?: Record<string, unknown>) =>
      request<unknown>({
        endpoint: "/sign-out",
        method: "POST",
        body: input ?? {},
      }),
    sendVerificationEmail: (input: Record<string, unknown>) =>
      request<unknown>({
        endpoint: "/send-verification-email",
        method: "POST",
        body: input,
      }),
    forgetPassword,
    requestPasswordReset: forgetPassword,
    admin: {
      oauthClient: {
        create: (input: DemoCreateOAuthClientInput) =>
          request<DemoOAuthClientRecord>({
            endpoint: "/admin/oauth-client/create",
            method: "POST",
            body: input,
          }),
        list: () =>
          request<DemoOAuthClientListResponse>({
            endpoint: "/admin/oauth-client/list",
            method: "GET",
          }),
      },
    },
    getSession,
    request,
    useSession: (options?: UseSessionOptions) =>
      useAthenaSession(
        {
          getSession: async () => getSession(),
        },
        options,
      ),
  };

  return client;
}

export function createDemoAuthClient(baseURL?: string): DemoAuthClient {
  return createAuthClient({ authApiOrigin: baseURL });
}
