import { Injectable, UnauthorizedException, BadRequestException, Inject } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { ConfigService } from "@nestjs/config";
import * as bcrypt from "bcrypt";
import { UsersFacade } from "@bff/modules/users/application/users.facade";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service";
import { WhmcsService } from "@bff/integrations/whmcs/whmcs.service";
import { SalesforceService } from "@bff/integrations/salesforce/salesforce.service";
import { AuditService, AuditAction } from "@bff/infra/audit/audit.service";
import { getErrorMessage } from "@bff/core/utils/error.util";
import { Logger } from "nestjs-pino";
import { sanitizeWhmcsRedirectPath } from "@bff/core/utils/sso.util";
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 { PrismaService } from "@bff/infra/database/prisma.service";
import { TokenBlacklistService } from "../infra/token/token-blacklist.service";
import { AuthTokenService } from "../infra/token/token.service";
import { AuthRateLimitService } from "../infra/rate-limiting/auth-rate-limit.service";
import { SignupWorkflowService } from "../infra/workflows/workflows/signup-workflow.service";
import { PasswordWorkflowService } from "../infra/workflows/workflows/password-workflow.service";
import { WhmcsLinkWorkflowService } from "../infra/workflows/workflows/whmcs-link-workflow.service";
import { mapPrismaUserToDomain } from "@bff/infra/mappers";

@Injectable()
export class AuthFacade {
  private readonly MAX_LOGIN_ATTEMPTS = 5;
  private readonly LOCKOUT_DURATION_MINUTES = 15;

  constructor(
    private readonly usersFacade: UsersFacade,
    private readonly mappingsService: MappingsService,
    private readonly jwtService: JwtService,
    private readonly configService: ConfigService,
    private readonly whmcsService: WhmcsService,
    private readonly salesforceService: SalesforceService,
    private readonly auditService: AuditService,
    private readonly tokenBlacklistService: TokenBlacklistService,
    private readonly prisma: PrismaService,
    private readonly signupWorkflow: SignupWorkflowService,
    private readonly passwordWorkflow: PasswordWorkflowService,
    private readonly whmcsLinkWorkflow: WhmcsLinkWorkflowService,
    private readonly tokenService: AuthTokenService,
    private readonly authRateLimitService: AuthRateLimitService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async healthCheck() {
    const health = {
      database: false,
      whmcs: false,
      salesforce: false,
      whmcsConfig: {
        baseUrl: !!this.configService.get("WHMCS_BASE_URL"),
        identifier: !!this.configService.get("WHMCS_API_IDENTIFIER"),
        secret: !!this.configService.get("WHMCS_API_SECRET"),
      },
      salesforceConfig: {
        connected: false,
      },
    };

    // Check database
    try {
      await this.usersFacade.findByEmail("health-check@test.com");
      health.database = true;
    } catch (error) {
      this.logger.debug("Database health check failed", { error: getErrorMessage(error) });
    }

    // Check WHMCS
    try {
      health.whmcs = await this.whmcsService.healthCheck();
    } catch (error) {
      this.logger.debug("WHMCS health check failed", { error: getErrorMessage(error) });
    }

    // Check Salesforce
    try {
      health.salesforceConfig.connected = this.salesforceService.healthCheck();
      health.salesforce = health.salesforceConfig.connected;
    } catch (error) {
      this.logger.debug("Salesforce health check failed", { error: getErrorMessage(error) });
    }

    return {
      status: health.database && health.whmcs && health.salesforce ? "healthy" : "degraded",
      services: health,
      timestamp: new Date().toISOString(),
    };
  }

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

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

  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);
    }
    // Update last login time and reset failed attempts
    await this.usersFacade.update(user.id, {
      lastLoginAt: new Date(),
      failedLoginAttempts: 0,
      lockedUntil: null,
    });

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

    const prismaUser = await this.usersFacade.findByIdInternal(user.id);
    if (!prismaUser) {
      throw new UnauthorizedException("User record missing");
    }

    const profile = mapPrismaUserToDomain(prismaUser);

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

    await this.updateAccountLastSignIn(user.id);

    return {
      user: {
        ...profile,
        role: prismaUser.role,
      },
      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> {
    const user = await this.usersFacade.findByEmailInternal(email);

    if (!user) {
      await this.auditService.logAuthEvent(
        AuditAction.LOGIN_FAILED,
        undefined,
        { reason: "User not found" },
        _request,
        false,
        "User not found"
      );
      return null;
    }

    // Check if account is locked
    if (user.lockedUntil && user.lockedUntil > new Date()) {
      await this.auditService.logAuthEvent(
        AuditAction.LOGIN_FAILED,
        user.id,
        { reason: "Account locked" },
        _request,
        false,
        "Account is locked"
      );
      return null;
    }

    if (!user.passwordHash) {
      await this.auditService.logAuthEvent(
        AuditAction.LOGIN_FAILED,
        user.id,
        { reason: "No password set" },
        _request,
        false,
        "No password set"
      );
      return null;
    }

    try {
      const isPasswordValid = await bcrypt.compare(password, user.passwordHash);

      if (isPasswordValid) {
        // Return sanitized user object matching the return type
        return {
          id: user.id,
          email: user.email,
          role: user.role,
        };
      } else {
        // Increment failed login attempts
        await this.handleFailedLogin(user, _request);
        return null;
      }
    } catch (error) {
      this.logger.error("Password validation error", {
        userId: user.id,
        error: getErrorMessage(error),
      });
      await this.auditService.logAuthEvent(
        AuditAction.LOGIN_FAILED,
        user.id,
        { error: getErrorMessage(error) },
        _request,
        false,
        getErrorMessage(error)
      );
      return null;
    }
  }

  private async handleFailedLogin(user: PrismaUser, _request?: Request): Promise<void> {
    const newFailedAttempts = (user.failedLoginAttempts || 0) + 1;
    let lockedUntil = null;
    let isAccountLocked = false;

    // Lock account if max attempts reached
    if (newFailedAttempts >= this.MAX_LOGIN_ATTEMPTS) {
      lockedUntil = new Date();
      lockedUntil.setMinutes(lockedUntil.getMinutes() + this.LOCKOUT_DURATION_MINUTES);
      isAccountLocked = true;
    }

    await this.usersFacade.update(user.id, {
      failedLoginAttempts: newFailedAttempts,
      lockedUntil,
    });

    // Log failed login
    await this.auditService.logAuthEvent(
      AuditAction.LOGIN_FAILED,
      user.id,
      {
        failedAttempts: newFailedAttempts,
        lockedUntil: lockedUntil?.toISOString(),
      },
      _request,
      false,
      "Invalid password"
    );

    // Log account lock if applicable
    if (isAccountLocked) {
      await this.auditService.logAuthEvent(
        AuditAction.ACCOUNT_LOCKED,
        user.id,
        {
          lockDuration: this.LOCKOUT_DURATION_MINUTES,
          lockedUntil: lockedUntil?.toISOString(),
        },
        _request,
        false,
        `Account locked for ${this.LOCKOUT_DURATION_MINUTES} minutes`
      );
    }
  }

  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: getErrorMessage(error),
        });
      }
    } else {
      this.logger.debug("Skipping refresh token revocation during logout – userId unavailable");
    }

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

  // Helper methods
  /**
   * Create SSO link to WHMCS for general access
   */
  async createSsoLink(userId: string, destination?: string): Promise<SsoLinkResponse> {
    try {
      // Production-safe logging - no sensitive data
      this.logger.log("Creating SSO link request");

      // Get WHMCS client ID from user mapping
      const mapping = await this.mappingsService.findByUserId(userId);

      if (!mapping?.whmcsClientId) {
        this.logger.warn("SSO link creation failed: No WHMCS mapping found");
        throw new UnauthorizedException("WHMCS client mapping not found");
      }

      // Create SSO token using custom redirect for better compatibility
      const ssoDestination = "sso:custom_redirect";
      const ssoRedirectPath = this.sanitizeWhmcsRedirectPath(destination);

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

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

      return result;
    } catch (error) {
      // Production-safe error logging - no sensitive data or stack traces
      this.logger.error("SSO link creation failed", {
        errorType: error instanceof Error ? error.constructor.name : "Unknown",
        message: getErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Ensure only safe, relative WHMCS paths are allowed for SSO redirects.
   * Falls back to 'clientarea.php' when input is missing or unsafe.
   */
  private sanitizeWhmcsRedirectPath(path?: string): string {
    return sanitizeWhmcsRedirectPath(path);
  }

  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: getErrorMessage(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) {
    // Normalize email
    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.usersFacade.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: getErrorMessage(e) });
    }

    // If already mapped, we can assume a WHMCS client exists
    if (mapped) {
      whmcsExists = true;
    } else {
      // Try a direct WHMCS lookup by email (best-effort)
      try {
        const client = await this.whmcsService.getClientDetailsByEmail(normalized);
        whmcsExists = !!client;
      } catch (e) {
        // Treat not found as no; other errors as unknown (leave whmcsExists false)
        this.logger.debug("Account status: WHMCS lookup", { error: getErrorMessage(e) });
      }
    }

    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; userAgent?: string }
  ) {
    if (!refreshToken) {
      throw new UnauthorizedException("Invalid refresh token");
    }

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

  /**
   * Preflight validation for signup. No side effects.
   * Returns a clear nextAction for the UI and detailed flags.
   */
  async signupPreflight(signupData: SignupRequest) {
    return this.signupWorkflow.signupPreflight(signupData);
  }
}
