import { GlobalStateStore, isLoggedInStore } from '@warkypublic/svelix';

const normalizeApiURL = (url: string): string => url.replace(/\/+$/, '');

const resolveApiURL = (envURL?: string): string => {
  const viteEnvURL =
    envURL?.trim() ||
    import.meta.env.VITE_API_URL?.trim() ||
    import.meta.env.VITE_API_BASE_URL?.trim() ||
    import.meta.env.VITE_URL?.trim();

  if (viteEnvURL) return normalizeApiURL(viteEnvURL);

  if (typeof window !== 'undefined') {
    return `${window.location.protocol}//${window.location.host}/api`;
  }

  const stateURL = GlobalStateStore.getState().session.apiURL?.trim();
  if (stateURL) return normalizeApiURL(stateURL);

  return '';
};

export { GlobalStateStore, isLoggedInStore };

export type OAuthClientRegistration = {
  client_id: string;
  client_name?: string;
  redirect_uris?: string[];
  grant_types?: string[];
  response_types?: string[];
  token_endpoint_auth_method?: string;
};

export type OAuthServerMetadata = {
  issuer: string;
  authorization_endpoint: string;
  token_endpoint: string;
  registration_endpoint: string;
  scopes_supported?: string[];
  response_types_supported?: string[];
  grant_types_supported?: string[];
  token_endpoint_auth_methods_supported?: string[];
  code_challenge_methods_supported?: string[];
};

export type OAuthSession = {
  clientId: string;
  redirectURI: string;
  codeVerifier: string;
  state: string;
  createdAt: number;
};

const OAUTH_SESSION_KEY = 'amcs.oauth.session';
const OAUTH_CLIENT_KEY = 'amcs.oauth.client';
const OAUTH_DEFAULT_SCOPE = 'mcp';

export function ensureApiURL(envURL?: string): string {
  const resolved = resolveApiURL(envURL);
  if (!resolved) return '';

  const state = GlobalStateStore.getState();
  if (state.session.apiURL !== resolved) {
    state.setApiURL(resolved);
  }

  return resolved;
}

export function getPublicBaseURL(): string {
  if (typeof window === 'undefined') return '';
  return `${window.location.protocol}//${window.location.host}`;
}

export function getOAuthRedirectURI(): string {
  const base = getPublicBaseURL();
  return base ? `${base}/oauth/callback` : '/oauth/callback';
}

function getStorage(storageKey: string): string | null {
  if (typeof window === 'undefined') return null;
  return window.localStorage.getItem(storageKey);
}

function setStorage(storageKey: string, value: string): void {
  if (typeof window === 'undefined') return;
  window.localStorage.setItem(storageKey, value);
}

function removeStorage(storageKey: string): void {
  if (typeof window === 'undefined') return;
  window.localStorage.removeItem(storageKey);
}

export function readOAuthClient(): OAuthClientRegistration | null {
  const raw = getStorage(OAUTH_CLIENT_KEY);
  if (!raw) return null;

  try {
    return JSON.parse(raw) as OAuthClientRegistration;
  } catch {
    removeStorage(OAUTH_CLIENT_KEY);
    return null;
  }
}

export function saveOAuthClient(client: OAuthClientRegistration): void {
  setStorage(OAUTH_CLIENT_KEY, JSON.stringify(client));
}

export function readOAuthSession(): OAuthSession | null {
  const raw = getStorage(OAUTH_SESSION_KEY);
  if (!raw) return null;

  try {
    return JSON.parse(raw) as OAuthSession;
  } catch {
    removeStorage(OAUTH_SESSION_KEY);
    return null;
  }
}

export function saveOAuthSession(session: OAuthSession): void {
  setStorage(OAUTH_SESSION_KEY, JSON.stringify(session));
}

export function clearOAuthSession(): void {
  removeStorage(OAUTH_SESSION_KEY);
}

export function setCurrentPath(pathname: string): void {
  const state = GlobalStateStore.getState();
  const current = state.navigation.currentPage ?? {};

  state.setCurrentPage({
    ...current,
    path: pathname
  });
}

function createRandomString(length = 48): string {
  const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';

  if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
    const bytes = new Uint8Array(length);
    crypto.getRandomValues(bytes);
    return Array.from(bytes, (byte) => alphabet[byte % alphabet.length]).join('');
  }

  return Array.from({ length }, () => alphabet[Math.floor(Math.random() * alphabet.length)]).join('');
}

function base64UrlEncode(buffer: ArrayBuffer): string {
  let binary = '';
  const bytes = new Uint8Array(buffer);
  const chunkSize = 0x8000;

  for (let index = 0; index < bytes.length; index += chunkSize) {
    binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
  }

  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}

async function sha256(input: string): Promise<string> {
  if (typeof crypto === 'undefined' || !crypto.subtle) {
    throw new Error('Secure browser crypto is required for OAuth login.');
  }

  const data = new TextEncoder().encode(input);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return base64UrlEncode(digest);
}

export async function fetchOAuthMetadata(): Promise<OAuthServerMetadata> {
  const base = getPublicBaseURL();
  const response = await fetch(`${base}/.well-known/oauth-authorization-server`);
  if (!response.ok) {
    throw new Error(`Failed to load OAuth metadata (${response.status})`);
  }

  return (await response.json()) as OAuthServerMetadata;
}

export async function ensureOAuthClientRegistration(metadata: OAuthServerMetadata): Promise<OAuthClientRegistration> {
  const redirectURI = getOAuthRedirectURI();

  const response = await fetch(metadata.registration_endpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      client_name: 'AMCS Admin UI',
      redirect_uris: [redirectURI],
      grant_types: ['authorization_code'],
      response_types: ['code'],
      token_endpoint_auth_method: 'none'
    })
  });

  if (!response.ok) {
    throw new Error(`Failed to register OAuth client (${response.status})`);
  }

  const client = (await response.json()) as OAuthClientRegistration;
  saveOAuthClient(client);
  return client;
}

export async function buildOAuthAuthorizationURL(): Promise<string> {
  removeStorage(OAUTH_CLIENT_KEY);
  const metadata = await fetchOAuthMetadata();
  const client = await ensureOAuthClientRegistration(metadata);
  const codeVerifier = createRandomString(96);
  const codeChallenge = await sha256(codeVerifier);
  const state = createRandomString(40);
  const redirectURI = getOAuthRedirectURI();

  saveOAuthSession({
    clientId: client.client_id,
    redirectURI,
    codeVerifier,
    state,
    createdAt: Date.now()
  });

  const url = new URL(metadata.authorization_endpoint);
  url.searchParams.set('client_id', client.client_id);
  url.searchParams.set('redirect_uri', redirectURI);
  url.searchParams.set('response_type', 'code');
  url.searchParams.set('scope', OAUTH_DEFAULT_SCOPE);
  url.searchParams.set('state', state);
  url.searchParams.set('code_challenge', codeChallenge);
  url.searchParams.set('code_challenge_method', 'S256');

  return url.toString();
}

export async function loginWithCredentials(username: string, password: string): Promise<string> {
  const base = getPublicBaseURL();
  const response = await fetch(`${base}/api/oauth/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: username,
      client_secret: password
    })
  });

  const payload = (await response.json()) as { access_token?: string; error?: string };
  if (!response.ok || !payload.access_token) {
    throw new Error(payload.error || `Login failed (${response.status})`);
  }

  return payload.access_token;
}

export async function exchangeOAuthCode(code: string, returnedState: string): Promise<string> {
  const session = readOAuthSession();
  if (!session) {
    throw new Error('OAuth session is missing. Start login again.');
  }

  if (session.state !== returnedState) {
    throw new Error('OAuth state mismatch. Start login again.');
  }

  const metadata = await fetchOAuthMetadata();
  const response = await fetch(metadata.token_endpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: session.redirectURI,
      client_id: session.clientId,
      code_verifier: session.codeVerifier
    })
  });

  const payload = (await response.json()) as { access_token?: string; error?: string };
  if (!response.ok || !payload.access_token) {
    throw new Error(payload.error || `Token exchange failed (${response.status})`);
  }

  clearOAuthSession();
  return payload.access_token;
}
