import { Injectable, Inject, NotFoundException, ForbiddenException } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import {
  SUPPORT_CASE_PRIORITY,
  SUPPORT_CASE_STATUS,
  type SupportCase,
  type SupportCaseFilter,
  type SupportCaseList,
  type CreateCaseRequest,
  type CreateCaseResponse,
  type PublicContactRequest,
  type CaseMessageList,
  type AddCaseCommentRequest,
  type AddCaseCommentResponse,
} from "@customer-portal/domain/support";
import { SALESFORCE_CASE_ORIGIN } from "@customer-portal/domain/support/providers";
import { SalesforceCaseService } from "@bff/integrations/salesforce/services/salesforce-case.service.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { SupportCacheService } from "./support-cache.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { hashEmailForLogs } from "@bff/core/logging/redaction.util.js";

/**
 * Status values that indicate an open/active case
 * (Display values after mapping from Salesforce API names)
 */
const OPEN_STATUSES: string[] = [
  SUPPORT_CASE_STATUS.NEW,
  SUPPORT_CASE_STATUS.IN_PROGRESS,
  SUPPORT_CASE_STATUS.AWAITING_CUSTOMER,
];

/**
 * Status values that indicate a resolved/closed case
 * (Display values after mapping from Salesforce API names)
 */
const RESOLVED_STATUSES: string[] = [SUPPORT_CASE_STATUS.CLOSED];

/**
 * Priority values that indicate high priority
 */
const HIGH_PRIORITIES: string[] = [SUPPORT_CASE_PRIORITY.HIGH];

@Injectable()
export class SupportService {
  constructor(
    private readonly caseService: SalesforceCaseService,
    private readonly mappingsService: MappingsService,
    private readonly cacheService: SupportCacheService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * List cases for a user with optional filters
   *
   * Uses Redis caching with 2-minute TTL to reduce Salesforce API calls.
   * Cache is invalidated when customer creates a new case.
   */
  async listCases(userId: string, filters?: SupportCaseFilter): Promise<SupportCaseList> {
    const accountId = await this.getAccountIdForUser(userId);

    try {
      // Use cache with TTL (no CDC events for cases)
      const caseList = await this.cacheService.getCaseList(accountId, async () => {
        const cases = await this.caseService.getCasesForAccount(accountId);
        const summary = this.buildSummary(cases);
        return { cases, summary };
      });

      // Apply filters after cache (filters are user-specific, cache is account-level)
      if (filters && Object.keys(filters).length > 0) {
        const filteredCases = this.applyFilters(caseList.cases, filters);
        const summary = this.buildSummary(filteredCases);
        return { cases: filteredCases, summary };
      }

      return caseList;
    } catch (error) {
      this.logger.error("Failed to list support cases", {
        userId,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Get a single case by ID
   */
  async getCase(userId: string, caseId: string): Promise<SupportCase> {
    const accountId = await this.getAccountIdForUser(userId);

    try {
      // SalesforceCaseService now returns SupportCase directly using domain mappers
      const supportCase = await this.caseService.getCaseById(caseId, accountId);

      if (!supportCase) {
        throw new NotFoundException("Support case not found");
      }

      return supportCase;
    } catch (error) {
      if (error instanceof NotFoundException) {
        throw error;
      }
      this.logger.error("Failed to get support case", {
        userId,
        caseId,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Create a new support case
   *
   * Invalidates case list cache after successful creation.
   */
  async createCase(userId: string, request: CreateCaseRequest): Promise<CreateCaseResponse> {
    const accountId = await this.getAccountIdForUser(userId);

    try {
      const result = await this.caseService.createCase({
        subject: request.subject,
        description: request.description,
        ...(request.priority === undefined ? {} : { priority: request.priority }),
        accountId,
        origin: SALESFORCE_CASE_ORIGIN.PORTAL_SUPPORT,
      });

      // Invalidate cache so new case appears immediately
      await this.cacheService.invalidateCaseList(accountId);

      this.logger.log("Support case created", {
        userId,
        caseId: result.id,
        caseNumber: result.caseNumber,
      });

      return result;
    } catch (error) {
      this.logger.error("Failed to create support case", {
        userId,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Create a contact request from public form (no authentication required)
   * Creates a Web-to-Case in Salesforce or sends an email notification
   */
  async createPublicContactRequest(request: PublicContactRequest): Promise<void> {
    const emailHash = hashEmailForLogs(request.email);
    this.logger.log("Creating public contact request", { emailHash });

    try {
      // Create a case without account association (Web-to-Case style)
      await this.caseService.createWebCase({
        subject: `Contact from ${request.name}`,
        description: `Contact from: ${request.name}\nEmail: ${request.email}\nPhone: ${request.phone || "Not provided"}\n\n${request.message}`,
        suppliedEmail: request.email,
        suppliedName: request.name,
        ...(request.phone === undefined ? {} : { suppliedPhone: request.phone }),
        origin: "Web",
        priority: "Medium",
      });

      this.logger.log("Public contact request created successfully", {
        emailHash,
      });
    } catch (error) {
      this.logger.error("Failed to create public contact request", {
        error: extractErrorMessage(error),
        emailHash,
      });
      // Don't throw - we don't want to expose internal errors to public users
      // In production, this should send a fallback email notification
    }
  }

  // ============================================================================
  // Case Messages (Conversation) Methods
  // ============================================================================

  /**
   * Get all messages for a case (conversation view)
   *
   * Returns a unified timeline of EmailMessages and public CaseComments.
   * Uses Redis caching with 1-minute TTL for active conversations.
   * Cache is invalidated when customer adds a comment.
   *
   * @param userId - Portal user ID
   * @param caseId - Salesforce Case ID
   * @param customerEmail - Customer's email for identifying their messages
   */
  async getCaseMessages(
    userId: string,
    caseId: string,
    customerEmail?: string
  ): Promise<CaseMessageList> {
    const accountId = await this.getAccountIdForUser(userId);

    try {
      // Use cache with short TTL for messages (fresher for active conversations)
      const messages = await this.cacheService.getCaseMessages(caseId, async () => {
        return this.caseService.getCaseMessages(caseId, accountId, customerEmail);
      });

      return messages;
    } catch (error) {
      if (error instanceof NotFoundException) {
        throw error;
      }
      this.logger.error("Failed to get case messages", {
        userId,
        caseId,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Add a comment to a case (customer reply via portal)
   *
   * Creates a public CaseComment visible to both customer and agents.
   * Invalidates messages cache after successful comment so it appears immediately.
   */
  async addCaseComment(
    userId: string,
    caseId: string,
    request: AddCaseCommentRequest
  ): Promise<AddCaseCommentResponse> {
    const accountId = await this.getAccountIdForUser(userId);

    try {
      const result = await this.caseService.addCaseComment(caseId, accountId, request.body);

      // Invalidate caches so new comment appears immediately
      await this.cacheService.invalidateAllForAccount(accountId, caseId);

      this.logger.log("Case comment added", {
        userId,
        caseId,
        commentId: result.id,
      });

      return result;
    } catch (error) {
      this.logger.error("Failed to add case comment", {
        userId,
        caseId,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Get Salesforce account ID for a user
   */
  private async getAccountIdForUser(userId: string): Promise<string> {
    const mapping = await this.mappingsService.findByUserId(userId);

    if (!mapping?.sfAccountId) {
      this.logger.warn("No Salesforce account mapping found for user", { userId });
      throw new ForbiddenException("Account not linked to Salesforce");
    }

    return mapping.sfAccountId;
  }

  /**
   * Apply filters to cases
   */
  private applyFilters(cases: SupportCase[], filters?: SupportCaseFilter): SupportCase[] {
    if (!filters) {
      return cases;
    }

    const search = filters.search?.toLowerCase().trim();

    return cases.filter(supportCase => {
      if (filters.status && supportCase.status !== filters.status) {
        return false;
      }
      if (filters.priority && supportCase.priority !== filters.priority) {
        return false;
      }
      if (filters.category && supportCase.category !== filters.category) {
        return false;
      }
      if (search) {
        const haystack =
          `${supportCase.subject} ${supportCase.description} ${supportCase.caseNumber}`.toLowerCase();
        if (!haystack.includes(search)) {
          return false;
        }
      }
      return true;
    });
  }

  /**
   * Build summary statistics for cases
   */
  private buildSummary(cases: SupportCase[]): SupportCaseList["summary"] {
    const open = cases.filter(c => OPEN_STATUSES.includes(c.status)).length;
    const highPriority = cases.filter(c => HIGH_PRIORITIES.includes(c.priority)).length;
    const resolved = cases.filter(c => RESOLVED_STATUSES.includes(c.status)).length;

    return {
      total: cases.length,
      open,
      highPriority,
      resolved,
    };
  }
}
