import {authRepository} from '@/features/auth/repositories/authRepository';
import type {
  AuthSession,
  LoginCredentials,
  LoginError,
  LoginResult,
} from '@/features/auth/types/auth';

/**
 * The domain layer for authentication.
 *
 * It owns the RULES — what counts as valid input, what an HTTP status means in
 * user terms, what "signed out" looks like — and it owns them independently of
 * both the transport below it and the React tree above it. Nothing here is
 * async-framework-specific, so it is directly unit-testable and it survives a
 * backend swap untouched.
 */

const EMAIL_PATTERN = /^\S+@\S+\.\S+$/;

/**
 * Client-side validation, mirroring the server's.
 *
 * Duplicated on purpose, and the duplication is the point: this copy exists
 * for latency (no round trip to be told a field is blank), the server's copy
 * exists for correctness (a POST can arrive without ever passing through this
 * form). Neither is redundant, because they answer to different threats.
 */
export function validateCredentials(
  input: Pick<LoginCredentials, 'email' | 'password'>,
): LoginError | null {
  if (!input.email.trim()) {
    return {field: 'email', message: 'Enter your email address.'};
  }
  if (!EMAIL_PATTERN.test(input.email.trim())) {
    return {field: 'email', message: 'Enter a valid email address.'};
  }
  if (!input.password) {
    return {field: 'password', message: 'Enter your password.'};
  }
  return null;
}

/** Narrow the server's free-form `field` back onto the union. */
function toErrorField(field: string | undefined): LoginError['field'] {
  return field === 'email' || field === 'password' ? field : 'form';
}

export const authService = {
  /**
   * Validate, then attempt. Returns a discriminated result — callers cannot
   * accidentally treat a failure as a success, and nothing throws, because a
   * wrong password is an expected outcome of logging in, not an exception.
   */
  async login(credentials: LoginCredentials): Promise<LoginResult> {
    const invalid = validateCredentials(credentials);
    if (invalid) return {ok: false, error: invalid};

    const res = await authRepository.login(credentials);

    if (!res.ok || !res.data) {
      return {
        ok: false,
        error: {
          field: toErrorField(res.field),
          message:
            res.message ??
            (res.status === 0
              ? 'Could not reach the server. Check your connection.'
              : 'Sign-in failed. Please try again.'),
        },
      };
    }
    return {ok: true, session: res.data};
  },

  /**
   * Ends the server session. Resolves even when the request fails — a user who
   * pressed Log out must never be left looking at a workspace because the
   * network blipped; the client clears its own state and redirects regardless,
   * and the cookie's own expiry is the backstop.
   */
  async logout(): Promise<void> {
    try {
      await authRepository.logout();
    } catch {
      /* deliberately ignored — see above */
    }
  },

  /**
   * Who the server says we are. `null` is a valid answer (signed out), so it
   * is returned as data rather than raised as an error.
   */
  async currentSession(): Promise<AuthSession | null> {
    const res = await authRepository.currentSession();
    return res.ok ? (res.data ?? null) : null;
  },
};
