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

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

import {
    ForgotPasswordUseCase,
    type ForgotPasswordApplicationError,
    type ForgotPasswordInput,
} from '@core/identity/application/use-cases/ForgotPasswordUseCase';
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 {
    ResetPasswordUseCase,
    type ResetPasswordApplicationError,
    type ResetPasswordInput,
} from '@core/identity/application/use-cases/ResetPasswordUseCase';
import {
    SignupSponsorUseCase,
    type SignupSponsorApplicationError,
    type SignupSponsorInput,
} from '@core/identity/application/use-cases/SignupSponsorUseCase';
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,
    FORGOT_PASSWORD_OUTPUT_PORT_TOKEN,
    FORGOT_PASSWORD_USE_CASE_TOKEN,
    IDENTITY_SESSION_PORT_TOKEN,
    LOGGER_TOKEN,
    LOGIN_USE_CASE_TOKEN,
    LOGOUT_USE_CASE_TOKEN,
    RESET_PASSWORD_OUTPUT_PORT_TOKEN,
    RESET_PASSWORD_USE_CASE_TOKEN,
    SIGNUP_SPONSOR_USE_CASE_TOKEN,
    SIGNUP_USE_CASE_TOKEN,
} from './AuthProviders';
import type { AuthSessionDTO } from './dtos/AuthDto';
import { LoginParamsDTO, SignupParamsDTO, SignupSponsorParamsDTO } from './dtos/AuthDto';
import { AuthSessionPresenter } from './presenters/AuthSessionPresenter';
import type { CommandResultDTO } from './presenters/CommandResultPresenter';
import { CommandResultPresenter } from './presenters/CommandResultPresenter';
import { ForgotPasswordPresenter } from './presenters/ForgotPasswordPresenter';
import { ResetPasswordPresenter } from './presenters/ResetPasswordPresenter';

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

function inferDemoRoleFromEmail(email: string): AuthSessionDTO['user']['role'] | undefined {
  const normalized = email.trim().toLowerCase();

  if (normalized === 'demo.driver@example.com') return 'driver';
  if (normalized === 'demo.sponsor@example.com') return 'sponsor';
  if (normalized === 'demo.owner@example.com') return 'league-owner';
  if (normalized === 'demo.steward@example.com') return 'league-steward';
  if (normalized === 'demo.admin@example.com') return 'league-admin';
  if (normalized === 'demo.systemowner@example.com') return 'system-owner';
  if (normalized === 'demo.superadmin@example.com') return 'super-admin';

  return undefined;
}

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(SIGNUP_SPONSOR_USE_CASE_TOKEN) private readonly signupSponsorUseCase: SignupSponsorUseCase,
    @Inject(LOGOUT_USE_CASE_TOKEN) private readonly logoutUseCase: LogoutUseCase,
    @Inject(FORGOT_PASSWORD_USE_CASE_TOKEN) private readonly forgotPasswordUseCase: ForgotPasswordUseCase,
    @Inject(RESET_PASSWORD_USE_CASE_TOKEN) private readonly resetPasswordUseCase: ResetPasswordUseCase,
    // 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,
    @Inject(FORGOT_PASSWORD_OUTPUT_PORT_TOKEN)
    private readonly forgotPasswordPresenter: ForgotPasswordPresenter,
    @Inject(RESET_PASSWORD_OUTPUT_PORT_TOKEN)
    private readonly resetPasswordPresenter: ResetPasswordPresenter,
  ) {}

  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;

    const userRole = coreSession.user.role;
    const role = userRole ? (userRole as AuthSessionDTO['user']['role']) : undefined;

    return {
      token: coreSession.token,
      user: {
        userId: coreSession.user.id,
        email: coreSession.user.email ?? '',
        displayName: coreSession.user.displayName,
        ...(coreSession.user.primaryDriverId ? { primaryDriverId: coreSession.user.primaryDriverId } : {}),
        ...(role !== undefined ? { role } : {}),
      },
    };
  }

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

    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 signupResult = result.unwrap();
    this.authSessionPresenter.present(signupResult);

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

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

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

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

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

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

    const signupResult = result.unwrap();
    this.authSessionPresenter.present(signupResult);

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

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

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

    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 loginResult = result.unwrap();
    this.authSessionPresenter.present(loginResult);

    const userDTO = this.authSessionPresenter.responseModel;
    const sessionOptions = params.rememberMe !== undefined
      ? { rememberMe: params.rememberMe }
      : undefined;

    const inferredRole = inferDemoRoleFromEmail(userDTO.email);

    const session = await this.identitySessionPort.createSession(
      {
        id: userDTO.userId,
        displayName: userDTO.displayName,
        email: userDTO.email,
        ...(userDTO.primaryDriverId ? { primaryDriverId: userDTO.primaryDriverId } : {}),
        ...(inferredRole ? { role: inferredRole } : {}),
      },
      sessionOptions
    );

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

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

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

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

    const logoutResult = result.unwrap();
    this.commandResultPresenter.present(logoutResult);

    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',
      },
    };
  }

  async forgotPassword(params: { email: string }): Promise<{ message: string; magicLink?: string }> {
    this.logger.debug(`[AuthService] Attempting forgot password for email: ${params.email}`);

    const input: ForgotPasswordInput = {
      email: params.email,
    };

    const executeResult = await this.forgotPasswordUseCase.execute(input);

    if (executeResult.isErr()) {
      const error = executeResult.unwrapErr() as ForgotPasswordApplicationError;
      throw new Error(mapApplicationErrorToMessage(error, 'Forgot password failed'));
    }

    const forgotPasswordResult = executeResult.unwrap();
    this.forgotPasswordPresenter.present(forgotPasswordResult);

    const response = this.forgotPasswordPresenter.responseModel;
    const result: { message: string; magicLink?: string } = {
      message: response.message,
    };
    if (response.magicLink) {
      result.magicLink = response.magicLink;
    }
    return result;
  }

  async resetPassword(params: { token: string; newPassword: string }): Promise<{ message: string }> {
    this.logger.debug('[AuthService] Attempting reset password');

    const input: ResetPasswordInput = {
      token: params.token,
      newPassword: params.newPassword,
    };

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

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

    const resetResult = result.unwrap();
    this.resetPasswordPresenter.present(resetResult);

    return this.resetPasswordPresenter.responseModel;
  }
}