import { Injectable, Inject } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { BaseServicesService } from "./base-services.service.js";
import { ServicesCacheService } from "./services-cache.service.js";
import type {
  InternetPlanCatalogItem,
  InternetInstallationCatalogItem,
  InternetAddonCatalogItem,
} from "@customer-portal/domain/services";
import type { SalesforceProduct2WithPricebookEntries } from "@customer-portal/domain/services/providers";
import {
  enrichInternetPlanMetadata,
  inferAddonTypeFromSku,
  inferInstallationTermFromSku,
} from "@customer-portal/domain/services";
import {
  mapInternetAddon,
  mapInternetInstallation,
  mapInternetPlan,
} from "@customer-portal/domain/services/providers";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { SalesforceConnection } from "@bff/integrations/salesforce/services/salesforce-connection.service.js";
import { Logger } from "nestjs-pino";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { InternetEligibilityService } from "./internet-eligibility.service.js";

@Injectable()
export class InternetServicesService extends BaseServicesService {
  constructor(
    sf: SalesforceConnection,
    config: ConfigService,
    @Inject(Logger) logger: Logger,
    private mappingsService: MappingsService,
    private catalogCache: ServicesCacheService,
    private eligibilityService: InternetEligibilityService
  ) {
    super(sf, config, logger);
  }

  async getPlans(): Promise<InternetPlanCatalogItem[]> {
    const cacheKey = this.catalogCache.buildServicesKey("internet", "plans");

    return this.catalogCache.getCachedServices(
      cacheKey,
      async () => {
        const soql = this.buildServicesQuery("Internet", [
          "Internet_Plan_Tier__c",
          "Internet_Offering_Type__c",
          "Catalog_Order__c",
        ]);
        const records = await this.executeQuery<SalesforceProduct2WithPricebookEntries>(
          soql,
          "Internet Plans"
        );

        const plans = records.map(record => {
          const entry = this.extractPricebookEntry(record);
          const plan = mapInternetPlan(record, entry);
          return enrichInternetPlanMetadata(plan);
        });

        // Prefer ordering by offering type (for services UX) over Product2.Name.
        // We still respect Catalog_Order__c (mapped to displayOrder) within each offering type.
        return plans.sort(compareInternetPlansForServices);
      },
      {
        resolveDependencies: plans => ({
          productIds: plans.map(plan => plan.id).filter((id): id is string => Boolean(id)),
        }),
      }
    );
  }

  async getInstallations(): Promise<InternetInstallationCatalogItem[]> {
    const cacheKey = this.catalogCache.buildServicesKey("internet", "installations");

    return this.catalogCache.getCachedServices(
      cacheKey,
      async () => {
        const soql = this.buildProductQuery("Internet", "Installation", [
          "Billing_Cycle__c",
          "Catalog_Order__c",
        ]);
        const records = await this.executeQuery<SalesforceProduct2WithPricebookEntries>(
          soql,
          "Internet Installations"
        );

        this.logger.log(`Found ${records.length} installation records`);

        return records
          .map(record => {
            const entry = this.extractPricebookEntry(record);
            const installation = mapInternetInstallation(record, entry);
            return {
              ...installation,
              catalogMetadata: {
                ...installation.catalogMetadata,
                installationTerm: inferInstallationTermFromSku(installation.sku ?? ""),
              },
            };
          })
          .sort((a, b) => (a.displayOrder ?? 0) - (b.displayOrder ?? 0));
      },
      {
        resolveDependencies: installations => ({
          productIds: installations.map(item => item.id).filter((id): id is string => Boolean(id)),
        }),
      }
    );
  }

  async getAddons(): Promise<InternetAddonCatalogItem[]> {
    const cacheKey = this.catalogCache.buildServicesKey("internet", "addons");

    return this.catalogCache.getCachedServices(
      cacheKey,
      async () => {
        const soql = this.buildProductQuery("Internet", "Add-on", [
          "Billing_Cycle__c",
          "Catalog_Order__c",
          "Bundled_Addon__c",
          "Is_Bundled_Addon__c",
        ]);
        const records = await this.executeQuery<SalesforceProduct2WithPricebookEntries>(
          soql,
          "Internet Add-ons"
        );

        this.logger.log(`Found ${records.length} addon records`);

        return records
          .map(record => {
            const entry = this.extractPricebookEntry(record);
            const addon = mapInternetAddon(record, entry);
            return {
              ...addon,
              catalogMetadata: {
                ...addon.catalogMetadata,
                addonType: inferAddonTypeFromSku(addon.sku ?? ""),
              },
            };
          })
          .sort((a, b) => (a.displayOrder ?? 0) - (b.displayOrder ?? 0));
      },
      {
        resolveDependencies: addons => ({
          productIds: addons.map(addon => addon.id).filter((id): id is string => Boolean(id)),
        }),
      }
    );
  }

  async getCatalogData() {
    const [plans, installations, addons] = await Promise.all([
      this.getPlans(),
      this.getInstallations(),
      this.getAddons(),
    ]);
    return { plans, installations, addons };
  }

  async getPlansForUser(userId: string): Promise<InternetPlanCatalogItem[]> {
    try {
      // Get all plans first
      const allPlans = await this.getPlans();

      // Get user's Salesforce account mapping
      const mapping = await this.mappingsService.findByUserId(userId);
      if (!mapping?.sfAccountId) {
        this.logger.warn(`No Salesforce mapping found for user ${userId}, returning all plans`);
        return allPlans;
      }

      // Get customer's eligibility from eligibility service
      const details = await this.eligibilityService.getEligibilityDetailsForUser(userId);

      if (!details) {
        this.logger.warn(`No Salesforce account found for user ${userId}, returning all plans`);
        return allPlans;
      }

      const eligibility = details.eligibility;

      if (!eligibility) {
        this.logger.log(`No eligibility field for user ${userId}, filtering to Home 1G plans only`);
        const homeGPlans = allPlans.filter(plan => plan.internetOfferingType === "Home 1G");
        return homeGPlans;
      }

      // Filter plans based on eligibility
      const eligiblePlans = allPlans.filter(plan => {
        const isEligible = this.eligibilityService.checkPlanEligibility(
          plan.internetOfferingType,
          eligibility
        );
        if (!isEligible) {
          this.logger.debug(
            `Plan ${plan.name} (${plan.internetPlanTier ?? "Unknown"}) not eligible for user ${userId} with eligibility: ${eligibility}`
          );
        }
        return isEligible;
      });

      this.logger.log(
        `Filtered ${allPlans.length} plans to ${eligiblePlans.length} eligible plans for user ${userId} with eligibility: ${eligibility}`
      );
      return eligiblePlans;
    } catch (error) {
      this.logger.error(`Failed to get eligible plans for user ${userId}`, {
        error: extractErrorMessage(error),
      });
      // Fallback to all plans if there's an error
      return this.getPlans();
    }
  }
}

function normalizeCatalogString(value: unknown): string {
  return typeof value === "string" ? value.trim().toLowerCase() : "";
}

function compareInternetPlansForServices(
  a: InternetPlanCatalogItem,
  b: InternetPlanCatalogItem
): number {
  const aOffering = normalizeCatalogString(a.internetOfferingType);
  const bOffering = normalizeCatalogString(b.internetOfferingType);
  if (aOffering !== bOffering) return aOffering.localeCompare(bOffering);

  const aOrder = typeof a.displayOrder === "number" ? a.displayOrder : Number.MAX_SAFE_INTEGER;
  const bOrder = typeof b.displayOrder === "number" ? b.displayOrder : Number.MAX_SAFE_INTEGER;
  if (aOrder !== bOrder) return aOrder - bOrder;

  const aName = normalizeCatalogString(a.name);
  const bName = normalizeCatalogString(b.name);
  return aName.localeCompare(bName);
}
