import { Inject } from '@nestjs/common';

import type { Logger } from '@core/shared/application';

import {
  LoginUseCase,
  type LoginApplicationError,
  type LoginInput,
} from '@core/identity/application/use-cases/LoginUseCase';
import { LogoutUseCase, type LogoutApplicationError } from '@core/identity/application/use-cases/LogoutUseCase';
import {
  SignupUseCase,
  type SignupApplicationError,
  type SignupInput,
} from '@core/identity/application/use-cases/SignupUseCase';

import type { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';

import {
  AUTH_SESSION_OUTPUT_PORT_TOKEN,
  COMMAND_RESULT_OUTPUT_PORT_TOKEN,
  IDENTITY_SESSION_PORT_TOKEN,
  LOGGER_TOKEN,
  LOGIN_USE_CASE_TOKEN,
  LOGOUT_USE_CASE_TOKEN,
  SIGNUP_USE_CASE_TOKEN,
} from './AuthProviders';
import type { AuthSessionDTO } from './dtos/AuthDto';
import { LoginParamsDTO, SignupParamsDTO } from './dtos/AuthDto';
import { AuthSessionPresenter } from './presenters/AuthSessionPresenter';
import type { CommandResultDTO } from './presenters/CommandResultPresenter';
import { CommandResultPresenter } from './presenters/CommandResultPresenter';

function mapApplicationErrorToMessage(error: { details?: { message?: string } } | undefined, fallback: string): string {
  return error?.details?.message ?? fallback;
}

export class AuthService {
  constructor(
    @Inject(LOGGER_TOKEN) private readonly logger: Logger,
    @Inject(IDENTITY_SESSION_PORT_TOKEN)
    private readonly identitySessionPort: IdentitySessionPort,
    @Inject(LOGIN_USE_CASE_TOKEN) private readonly loginUseCase: LoginUseCase,
    @Inject(SIGNUP_USE_CASE_TOKEN) private readonly signupUseCase: SignupUseCase,
    @Inject(LOGOUT_USE_CASE_TOKEN) private readonly logoutUseCase: LogoutUseCase,
    // TODO presenters must not be injected
    @Inject(AUTH_SESSION_OUTPUT_PORT_TOKEN)
    private readonly authSessionPresenter: AuthSessionPresenter,
    @Inject(COMMAND_RESULT_OUTPUT_PORT_TOKEN)
    private readonly commandResultPresenter: CommandResultPresenter,
  ) {}

  async getCurrentSession(): Promise<AuthSessionDTO | null> {
    // TODO must call a use case
    this.logger.debug('[AuthService] Attempting to get current session.');
    const coreSession = await this.identitySessionPort.getCurrentSession();
    if (!coreSession) return null;

    // TODO!!
    return {
      token: coreSession.token,
      user: {
        userId: coreSession.user.id,
        email: coreSession.user.email ?? '',
        displayName: coreSession.user.displayName,
      },
    };
  }

  async signupWithEmail(params: SignupParamsDTO): Promise<AuthSessionDTO> {
    this.logger.debug(`[AuthService] Attempting signup for email: ${params.email}`);

    this.authSessionPresenter.reset();

    const input: SignupInput = {
      email: params.email,
      password: params.password,
      displayName: params.displayName,
    };

    const result = await this.signupUseCase.execute(input);

    if (result.isErr()) {
      const error = result.unwrapErr() as SignupApplicationError;
      throw new Error(mapApplicationErrorToMessage(error, 'Signup failed'));
    }

    const userDTO = this.authSessionPresenter.responseModel;
    const session = await this.identitySessionPort.createSession({
      id: userDTO.userId,
      displayName: userDTO.displayName,
      email: userDTO.email,
    });

    return {
      token: session.token,
      user: userDTO,
    };
  }

  async loginWithEmail(params: LoginParamsDTO): Promise<AuthSessionDTO> {
    this.logger.debug(`[AuthService] Attempting login for email: ${params.email}`);

    this.authSessionPresenter.reset();

    const input: LoginInput = {
      email: params.email,
      password: params.password,
    };

    const result = await this.loginUseCase.execute(input);

    if (result.isErr()) {
      const error = result.unwrapErr() as LoginApplicationError;
      throw new Error(mapApplicationErrorToMessage(error, 'Login failed'));
    }

    const userDTO = this.authSessionPresenter.responseModel;
    const session = await this.identitySessionPort.createSession({
      id: userDTO.userId,
      displayName: userDTO.displayName,
      email: userDTO.email,
    });

    return {
      token: session.token,
      user: userDTO,
    };
  }

  async logout(): Promise<CommandResultDTO> {
    this.logger.debug('[AuthService] Attempting logout.');

    this.commandResultPresenter.reset();

    const result = await this.logoutUseCase.execute();

    if (result.isErr()) {
      const error = result.unwrapErr() as LogoutApplicationError;
      throw new Error(mapApplicationErrorToMessage(error, 'Logout failed'));
    }

    return this.commandResultPresenter.responseModel;
  }

  /**
   * Start iRacing OAuth flow.
   *
   * NOTE: This is a placeholder implementation for the current alpha build.
   * A production implementation should delegate to a dedicated iRacing OAuth port
   * and persist/validate state server-side.
   */
  async startIracingAuth(returnTo?: string): Promise<string> {
    this.logger.debug('[AuthService] Starting iRacing auth flow', { returnTo });

    const state = Math.random().toString(36).slice(2);
    const base = 'https://example.com/iracing/auth';
    const query = new URLSearchParams();
    query.set('state', state);
    if (returnTo) {
      query.set('returnTo', returnTo);
    }

    return `${base}?${query.toString()}`;
  }

  /**
   * Handle iRacing OAuth callback.
   *
   * NOTE: Placeholder implementation that creates a demo session.
   */
  async iracingCallback(code: string, state: string, returnTo?: string): Promise<AuthSessionDTO> {
    this.logger.debug('[AuthService] iRacing callback received', { hasCode: !!code, state, returnTo });

    const userId = `iracing-${state || code}`.slice(0, 64);

    const session = await this.identitySessionPort.createSession({
      id: userId,
      displayName: 'iRacing User',
      email: '',
    });

    return {
      token: session.token,
      user: {
        userId,
        email: '',
        displayName: 'iRacing User',
      },
    };
  }
}
