import { Injectable, UnauthorizedException, BadRequestException, Inject } from "@nestjs/common";
import { UsersService } from "@bff/modules/users/application/users.service.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { WhmcsAccountDiscoveryService } from "@bff/integrations/whmcs/services/whmcs-account-discovery.service.js";
import { WhmcsSsoService } from "@bff/integrations/whmcs/services/whmcs-sso.service.js";
import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js";
import { AuditService, AuditAction } from "@bff/infra/audit/audit.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { Logger } from "nestjs-pino";
import { sanitizeWhmcsRedirectPath } from "@bff/core/utils/sso.util.js";
import {
  type SignupRequest,
  type ValidateSignupRequest,
  type LinkWhmcsRequest,
  type SetPasswordRequest,
  type ChangePasswordRequest,
  type SsoLinkResponse,
} from "@customer-portal/domain/auth";

import type { User as PrismaUser } from "@prisma/client";
import type { Request } from "express";
import { TokenBlacklistService } from "../infra/token/token-blacklist.service.js";
import { AuthTokenService } from "../infra/token/token.service.js";
import { AuthRateLimitService } from "../infra/rate-limiting/auth-rate-limit.service.js";
import { SignupWorkflowService } from "../infra/workflows/signup-workflow.service.js";
import { PasswordWorkflowService } from "../infra/workflows/password-workflow.service.js";
import { WhmcsLinkWorkflowService } from "../infra/workflows/whmcs-link-workflow.service.js";
// mapPrismaUserToDomain removed - usersService.update now returns profile directly
import { AuthHealthService } from "./auth-health.service.js";
import { AuthLoginService } from "./auth-login.service.js";

/**
 * Auth Orchestrator
 *
 * Application layer orchestrator that coordinates authentication operations.
 * Delegates to specialized services for specific functionality:
 * - AuthHealthService: Health checks
 * - AuthLoginService: Login validation
 * - SignupWorkflowService: Signup flow
 * - PasswordWorkflowService: Password operations
 * - WhmcsLinkWorkflowService: WHMCS account linking
 */
@Injectable()
export class AuthOrchestrator {
  constructor(
    private readonly usersService: UsersService,
    private readonly mappingsService: MappingsService,
    private readonly whmcsSsoService: WhmcsSsoService,
    private readonly discoveryService: WhmcsAccountDiscoveryService,
    private readonly salesforceService: SalesforceFacade,
    private readonly auditService: AuditService,
    private readonly tokenBlacklistService: TokenBlacklistService,
    private readonly signupWorkflow: SignupWorkflowService,
    private readonly passwordWorkflow: PasswordWorkflowService,
    private readonly whmcsLinkWorkflow: WhmcsLinkWorkflowService,
    private readonly tokenService: AuthTokenService,
    private readonly authRateLimitService: AuthRateLimitService,
    private readonly healthService: AuthHealthService,
    private readonly loginService: AuthLoginService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async healthCheck() {
    return this.healthService.check();
  }

  async validateSignup(validateData: ValidateSignupRequest, request?: Request) {
    return this.signupWorkflow.validateSignup(validateData, request);
  }

  async signup(signupData: SignupRequest, request?: Request) {
    return this.signupWorkflow.signup(signupData, request);
  }

  /**
   * Original login method - validates credentials and completes login
   * Used by LocalAuthGuard flow
   */
  async login(
    user: {
      id: string;
      email: string;
      role?: string;
      passwordHash?: string | null;
      failedLoginAttempts?: number | null;
      lockedUntil?: Date | null;
    },
    request?: Request
  ) {
    if (request) {
      await this.authRateLimitService.clearLoginAttempts(request);
    }

    return this.completeLogin(
      { id: user.id, email: user.email, role: user.role ?? "USER" },
      request
    );
  }

  /**
   * Complete login after credential validation (and OTP verification if required)
   * Generates tokens and updates user state
   *
   * @param user - Validated user info (id, email, role)
   * @param request - Express request for audit logging
   */
  async completeLogin(user: { id: string; email: string; role: string }, request?: Request) {
    // Update last login time and reset failed attempts
    // usersService.update returns the updated profile, avoiding a second DB fetch
    const profile = await this.usersService.update(user.id, {
      lastLoginAt: new Date(),
      failedLoginAttempts: 0,
      lockedUntil: null,
    });

    await this.auditService.logAuthEvent(
      AuditAction.LOGIN_SUCCESS,
      user.id,
      { email: user.email },
      request,
      true
    );

    const userAgent = request?.headers?.["user-agent"];
    const tokens = await this.tokenService.generateTokenPair(
      {
        id: profile.id,
        email: profile.email,
        role: user.role as "USER" | "ADMIN",
      },
      userAgent ? { userAgent } : {}
    );

    await this.updateAccountLastSignIn(user.id);

    return {
      user: {
        ...profile,
        role: user.role as "USER" | "ADMIN",
      },
      tokens,
    };
  }

  async linkWhmcsUser(linkData: LinkWhmcsRequest) {
    return this.whmcsLinkWorkflow.linkWhmcsUser(linkData.email, linkData.password);
  }

  async checkPasswordNeeded(email: string) {
    return this.passwordWorkflow.checkPasswordNeeded(email);
  }

  async setPassword(setPasswordData: SetPasswordRequest) {
    return this.passwordWorkflow.setPassword(setPasswordData.email, setPasswordData.password);
  }

  async validateUser(
    email: string,
    password: string,
    request?: Request
  ): Promise<{ id: string; email: string; role: string } | null> {
    return this.loginService.validateUser(email, password, request);
  }

  async logout(userId?: string, token?: string, request?: Request): Promise<void> {
    if (token) {
      await this.tokenBlacklistService.blacklistToken(token);
    }

    if (userId) {
      try {
        await this.tokenService.revokeAllUserTokens(userId);
      } catch (error) {
        this.logger.warn("Failed to revoke refresh tokens during logout", {
          userId,
          error: extractErrorMessage(error),
        });
      }
    } else {
      this.logger.debug("Skipping refresh token revocation during logout – userId unavailable");
    }

    await this.auditService.logAuthEvent(AuditAction.LOGOUT, userId, {}, request, true);
  }

  /**
   * Create SSO link to WHMCS for general access
   */
  async createSsoLink(userId: string, destination?: string): Promise<SsoLinkResponse> {
    try {
      this.logger.log("Creating SSO link request");

      const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(userId);
      const ssoDestination = "sso:custom_redirect";
      const ssoRedirectPath = sanitizeWhmcsRedirectPath(destination);

      const result = await this.whmcsSsoService.createSsoToken(
        whmcsClientId,
        ssoDestination,
        ssoRedirectPath
      );

      this.logger.log("SSO link created successfully");

      return result;
    } catch (error) {
      this.logger.error("SSO link creation failed", {
        errorType: error instanceof Error ? error.constructor.name : "Unknown",
        message: extractErrorMessage(error),
      });
      throw error;
    }
  }

  private async updateAccountLastSignIn(userId: string): Promise<void> {
    try {
      const mapping = await this.mappingsService.findByUserId(userId);
      if (!mapping?.sfAccountId) {
        return;
      }

      await this.salesforceService.updateAccountPortalFields(mapping.sfAccountId, {
        lastSignedInAt: new Date(),
      });
    } catch (error) {
      this.logger.debug("Failed to update Salesforce last sign-in", {
        userId,
        error: extractErrorMessage(error),
      });
    }
  }

  async requestPasswordReset(email: string, request?: Request): Promise<void> {
    await this.passwordWorkflow.requestPasswordReset(email, request);
  }

  async resetPassword(token: string, newPassword: string) {
    return this.passwordWorkflow.resetPassword(token, newPassword);
  }

  async getAccountStatus(email: string) {
    const normalized = email?.toLowerCase().trim();
    if (!normalized || !normalized.includes("@")) {
      throw new BadRequestException("Valid email is required");
    }

    let portalUser: PrismaUser | null = null;
    let mapped = false;
    let whmcsExists = false;
    let needsPasswordSet = false;

    try {
      portalUser = await this.usersService.findByEmailInternal(normalized);
      if (portalUser) {
        mapped = await this.mappingsService.hasMapping(portalUser.id);
        needsPasswordSet = !portalUser.passwordHash;
      }
    } catch (e) {
      this.logger.warn("Account status: portal lookup failed", { error: extractErrorMessage(e) });
    }

    if (mapped) {
      whmcsExists = true;
    } else {
      const client = await this.discoveryService.findClientByEmail(normalized);
      whmcsExists = !!client;
    }

    let state: "none" | "portal_only" | "whmcs_only" | "both_mapped" = "none";
    if (portalUser && mapped) state = "both_mapped";
    else if (portalUser) state = "portal_only";
    else if (whmcsExists) state = "whmcs_only";

    const recommendedAction = (() => {
      switch (state) {
        case "both_mapped":
          return "sign_in" as const;
        case "portal_only":
          return needsPasswordSet ? ("set_password" as const) : ("sign_in" as const);
        case "whmcs_only":
          return "link_account" as const;
        case "none":
        default:
          return "sign_up" as const;
      }
    })();

    return {
      state,
      portalUserExists: !!portalUser,
      whmcsClientExists: whmcsExists,
      mapped,
      needsPasswordSet,
      recommendedAction,
    };
  }

  async changePassword(userId: string, data: ChangePasswordRequest, request?: Request) {
    return this.passwordWorkflow.changePassword(userId, data, request);
  }

  async refreshTokens(
    refreshToken: string | undefined,
    deviceInfo?: { deviceId?: string | undefined; userAgent?: string | undefined }
  ) {
    if (!refreshToken) {
      throw new UnauthorizedException("Invalid refresh token");
    }

    const { tokens, user } = await this.tokenService.refreshTokens(refreshToken, deviceInfo);
    return {
      user,
      tokens,
    };
  }

  /**
   * Preflight validation for signup
   */
  async signupPreflight(signupData: SignupRequest) {
    return this.signupWorkflow.signupPreflight(signupData);
  }
}
