import { Injectable, Inject, NotFoundException, BadRequestException } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import type { User as PrismaUser } from "@prisma/client";
import { getErrorMessage } from "@bff/core/utils/error.util";
import {
  Providers as CustomerProviders,
  addressSchema,
  combineToUser,
  type Address,
  type User,
} from "@customer-portal/domain/customer";
import {
  updateCustomerProfileRequestSchema,
  type UpdateCustomerProfileRequest,
} from "@customer-portal/domain/auth";
import type { Subscription } from "@customer-portal/domain/subscriptions";
import type { Invoice } from "@customer-portal/domain/billing";
import type { Activity, DashboardSummary, NextInvoice } from "@customer-portal/domain/dashboard";
import { dashboardSummarySchema } from "@customer-portal/domain/dashboard";
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 { validateUuidV4OrThrow } from "@customer-portal/domain/common";
import { UserAuthRepository } from "./user-auth.repository";

@Injectable()
export class UserProfileService {
  constructor(
    private readonly userAuthRepository: UserAuthRepository,
    private readonly mappingsService: MappingsService,
    private readonly whmcsService: WhmcsService,
    private readonly salesforceService: SalesforceService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async findById(userId: string): Promise<User | null> {
    const validId = validateUuidV4OrThrow(userId);
    const user = await this.userAuthRepository.findById(validId);
    if (!user) {
      return null;
    }
    return this.getProfileForUser(user);
  }

  async getProfile(userId: string): Promise<User> {
    const validId = validateUuidV4OrThrow(userId);
    const user = await this.userAuthRepository.findById(validId);
    if (!user) {
      throw new NotFoundException("User not found");
    }
    return this.getProfileForUser(user);
  }

  async getAddress(userId: string): Promise<Address | null> {
    const profile = await this.getProfile(userId);
    return profile.address ?? null;
  }

  async updateAddress(userId: string, addressUpdate: Partial<Address>): Promise<Address> {
    const validId = validateUuidV4OrThrow(userId);
    const parsed = addressSchema.partial().parse(addressUpdate ?? {});
    const hasUpdates = Object.values(parsed).some(value => value !== undefined);

    if (!hasUpdates) {
      throw new BadRequestException("No address fields provided for update");
    }

    const mapping = await this.mappingsService.findByUserId(validId);
    if (!mapping?.whmcsClientId) {
      throw new NotFoundException("WHMCS client mapping not found");
    }

    try {
      await this.whmcsService.updateClientAddress(mapping.whmcsClientId, parsed);
      await this.whmcsService.invalidateUserCache(validId);

      this.logger.log("Successfully updated customer address in WHMCS", {
        userId: validId,
        whmcsClientId: mapping.whmcsClientId,
      });

      const refreshedProfile = await this.getProfile(validId);
      if (refreshedProfile.address) {
        return refreshedProfile.address;
      }

      const refreshedAddress = await this.whmcsService.getClientAddress(mapping.whmcsClientId);
      return addressSchema.parse(refreshedAddress ?? {});
    } catch (error) {
      const msg = getErrorMessage(error);
      this.logger.error(
        { userId: validId, whmcsClientId: mapping.whmcsClientId, error: msg },
        "Failed to update customer address in WHMCS"
      );

      if (msg.includes("WHMCS API Error")) {
        throw new BadRequestException(msg.replace("WHMCS API Error: ", ""));
      }
      if (msg.includes("HTTP ")) {
        throw new BadRequestException("Upstream WHMCS error. Please try again.");
      }
      if (msg.includes("Missing required WHMCS configuration")) {
        throw new BadRequestException("Billing system not configured. Please contact support.");
      }

      throw new BadRequestException("Unable to update address.");
    }
  }

  async updateProfile(userId: string, update: UpdateCustomerProfileRequest): Promise<User> {
    const validId = validateUuidV4OrThrow(userId);
    const parsed = updateCustomerProfileRequestSchema.parse(update);

    try {
      const mapping = await this.mappingsService.findByUserId(validId);
      if (!mapping) {
        throw new NotFoundException("User mapping not found");
      }

      await this.whmcsService.updateClient(mapping.whmcsClientId, parsed);

      this.logger.log({ userId: validId }, "Successfully updated customer profile in WHMCS");

      return this.getProfile(validId);
    } catch (error) {
      const msg = getErrorMessage(error);
      this.logger.error(
        { userId: validId, error: msg },
        "Failed to update customer profile in WHMCS"
      );

      if (msg.includes("WHMCS API Error")) {
        throw new BadRequestException(msg.replace("WHMCS API Error: ", ""));
      }
      if (msg.includes("HTTP ")) {
        throw new BadRequestException("Upstream WHMCS error. Please try again.");
      }
      if (msg.includes("Missing required WHMCS configuration")) {
        throw new BadRequestException("Billing system not configured. Please contact support.");
      }

      throw new BadRequestException("Unable to update profile.");
    }
  }

  async getUserSummary(userId: string): Promise<DashboardSummary> {
    try {
      const user = await this.userAuthRepository.findById(userId);
      if (!user) {
        throw new NotFoundException("User not found");
      }

      const mapping = await this.mappingsService.findByUserId(userId);
      if (!mapping?.whmcsClientId) {
        this.logger.warn(`No WHMCS mapping found for user ${userId}`);

        const currency = "JPY";

        const summary: DashboardSummary = {
          stats: {
            activeSubscriptions: 0,
            unpaidInvoices: 0,
            openCases: 0,
            currency,
          },
          nextInvoice: null,
          recentActivity: [],
        };
        return summary;
      }

      const [subscriptionsData, invoicesData] = await Promise.allSettled([
        this.whmcsService.getSubscriptions(mapping.whmcsClientId, userId),
        this.whmcsService.getInvoices(mapping.whmcsClientId, userId, { limit: 50 }),
      ]);

      let activeSubscriptions = 0;
      let recentSubscriptions: Array<{
        id: number;
        status: string;
        registrationDate: string;
        productName: string;
      }> = [];
      if (subscriptionsData.status === "fulfilled") {
        const subscriptions: Subscription[] = subscriptionsData.value.subscriptions;
        activeSubscriptions = subscriptions.filter(sub => sub.status === "Active").length;
        recentSubscriptions = subscriptions
          .filter(sub => sub.status === "Active")
          .sort((a, b) => {
            const aTime = a.registrationDate
              ? new Date(a.registrationDate).getTime()
              : Number.NEGATIVE_INFINITY;
            const bTime = b.registrationDate
              ? new Date(b.registrationDate).getTime()
              : Number.NEGATIVE_INFINITY;
            return bTime - aTime;
          })
          .slice(0, 3)
          .map(sub => ({
            id: sub.id,
            status: sub.status,
            registrationDate: sub.registrationDate,
            productName: sub.productName,
          }));
      } else {
        this.logger.error(
          `Failed to fetch subscriptions for user ${userId}:`,
          subscriptionsData.reason
        );
      }

      let unpaidInvoices = 0;
      let nextInvoice: NextInvoice | null = null;
      let recentInvoices: Array<{
        id: number;
        status: string;
        dueDate?: string;
        total: number;
        number: string;
        issuedAt?: string;
        paidDate?: string;
        currency?: string | null;
      }> = [];
      if (invoicesData.status === "fulfilled") {
        const invoices: Invoice[] = invoicesData.value.invoices;

        unpaidInvoices = invoices.filter(
          inv => inv.status === "Unpaid" || inv.status === "Overdue"
        ).length;

        const upcomingInvoices = invoices
          .filter(inv => (inv.status === "Unpaid" || inv.status === "Overdue") && inv.dueDate)
          .sort((a, b) => {
            const aTime = a.dueDate ? new Date(a.dueDate).getTime() : Number.POSITIVE_INFINITY;
            const bTime = b.dueDate ? new Date(b.dueDate).getTime() : Number.POSITIVE_INFINITY;
            return aTime - bTime;
          });

        if (upcomingInvoices.length > 0) {
          const invoice = upcomingInvoices[0];
          nextInvoice = {
            id: invoice.id,
            dueDate: invoice.dueDate!,
            amount: invoice.total,
            currency: invoice.currency ?? "JPY",
          };
        }

        recentInvoices = invoices
          .sort((a, b) => {
            const aTime = a.issuedAt ? new Date(a.issuedAt).getTime() : Number.NEGATIVE_INFINITY;
            const bTime = b.issuedAt ? new Date(b.issuedAt).getTime() : Number.NEGATIVE_INFINITY;
            return bTime - aTime;
          })
          .slice(0, 5)
          .map(inv => ({
            id: inv.id,
            status: inv.status,
            dueDate: inv.dueDate,
            total: inv.total,
            number: inv.number,
            issuedAt: inv.issuedAt,
            currency: inv.currency ?? null,
          }));
      } else {
        this.logger.error(`Failed to fetch invoices for user ${userId}`, {
          reason: getErrorMessage(invoicesData.reason),
        });
      }

      const activities: Activity[] = [];

      recentInvoices.forEach(invoice => {
        if (invoice.status === "Paid") {
          const metadata: Record<string, unknown> = {
            amount: invoice.total,
            currency: invoice.currency ?? "JPY",
          };
          if (invoice.dueDate) metadata.dueDate = invoice.dueDate;
          if (invoice.number) metadata.invoiceNumber = invoice.number;
          activities.push({
            id: `invoice-paid-${invoice.id}`,
            type: "invoice_paid",
            title: `Invoice #${invoice.number} paid`,
            description: `Payment of ¥${invoice.total.toLocaleString()} processed`,
            date: invoice.paidDate || invoice.issuedAt || new Date().toISOString(),
            relatedId: invoice.id,
            metadata,
          });
        } else if (invoice.status === "Unpaid" || invoice.status === "Overdue") {
          const metadata: Record<string, unknown> = {
            amount: invoice.total,
            currency: invoice.currency ?? "JPY",
            status: invoice.status,
          };
          if (invoice.dueDate) metadata.dueDate = invoice.dueDate;
          if (invoice.number) metadata.invoiceNumber = invoice.number;
          activities.push({
            id: `invoice-created-${invoice.id}`,
            type: "invoice_created",
            title: `Invoice #${invoice.number} created`,
            description: `Amount: ¥${invoice.total.toLocaleString()}`,
            date: invoice.issuedAt || new Date().toISOString(),
            relatedId: invoice.id,
            metadata,
          });
        }
      });

      recentSubscriptions.forEach(subscription => {
        const metadata: Record<string, unknown> = {
          productName: subscription.productName,
          status: subscription.status,
        };
        if (subscription.registrationDate) {
          metadata.registrationDate = subscription.registrationDate;
        }
        activities.push({
          id: `service-activated-${subscription.id}`,
          type: "service_activated",
          title: `${subscription.productName} activated`,
          description: "Service successfully provisioned",
          date: subscription.registrationDate,
          relatedId: subscription.id,
          metadata,
        });
      });

      activities.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
      const recentActivity = activities.slice(0, 10);

      this.logger.log(`Generated dashboard summary for user ${userId}`, {
        activeSubscriptions,
        unpaidInvoices,
        activitiesCount: recentActivity.length,
        hasNextInvoice: !!nextInvoice,
      });

      let currency = "JPY";
      try {
        const client = await this.whmcsService.getClientDetails(mapping.whmcsClientId);
        const resolvedCurrency =
          typeof client.currency_code === "string" && client.currency_code.trim().length > 0
            ? client.currency_code
            : null;
        if (resolvedCurrency) {
          currency = resolvedCurrency;
        }
      } catch (error) {
        this.logger.warn("Could not fetch currency from WHMCS client", {
          userId,
          error: getErrorMessage(error),
        });
      }

      const summary: DashboardSummary = {
        stats: {
          activeSubscriptions,
          unpaidInvoices,
          openCases: 0,
          currency,
        },
        nextInvoice,
        recentActivity,
      };
      return dashboardSummarySchema.parse(summary);
    } catch (error) {
      this.logger.error(`Failed to get user summary for ${userId}`, {
        error: getErrorMessage(error),
      });
      throw new BadRequestException("Unable to retrieve dashboard summary");
    }
  }

  private async getProfileForUser(user: PrismaUser): Promise<User> {
    const mapping = await this.mappingsService.findByUserId(user.id);
    if (!mapping?.whmcsClientId) {
      throw new NotFoundException("WHMCS client mapping not found");
    }

    try {
      const whmcsClient = await this.whmcsService.getClientDetails(mapping.whmcsClientId);
      const userAuth = CustomerProviders.Portal.mapPrismaUserToUserAuth(user);
      return combineToUser(userAuth, whmcsClient);
    } catch (error) {
      this.logger.error("Failed to fetch client profile from WHMCS", {
        error: getErrorMessage(error),
        userId: user.id,
        whmcsClientId: mapping.whmcsClientId,
      });
      throw new BadRequestException("Unable to retrieve customer profile from billing system");
    }
  }
}
