import { randomUUID } from 'node:crypto';

import {
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';

import type {
  WhmcsImportServiceRequest,
  WhmcsListAccountsResponse,
  WhmcsReconcileRequest,
  WhmcsReconcileResponse,
} from '@hexahost/contracts';
import { formatJoinAddress } from '@hexahost/dns';
import type { Prisma } from '@hexahost/database';

import { PrismaService } from '../../prisma/prisma.service';

import { WhmcsEventsService } from './whmcs-events.service';

type InstallationRef = {
  id: string;
  integrationId: string;
};

type ReconciliationSeverity =
  | 'INFO'
  | 'WARNING'
  | 'BLOCKING'
  | 'DESTRUCTIVE'
  | 'SECURITY';

type IssueDraft = {
  code: string;
  severity: ReconciliationSeverity;
  message: string;
  externalServiceId?: string;
  serverId?: string;
  details?: Record<string, unknown>;
};

@Injectable()
export class WhmcsReconciliationService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly events: WhmcsEventsService,
  ) {}

  async listAccounts(installation: InstallationRef): Promise<WhmcsListAccountsResponse> {
    const links = await this.prisma.whmcsServiceLink.findMany({
      where: { installationId: installation.id },
      include: {
        server: { include: { plan: true } },
      },
      orderBy: { externalServiceId: 'asc' },
    });

    const clientLinks = await this.prisma.whmcsClientLink.findMany({
      where: { installationId: installation.id },
    });
    const clientByUserId = new Map(clientLinks.map((link) => [link.userId, link]));

    return {
      accounts: links.map((link) => ({
        externalServiceId: link.externalServiceId,
        externalClientId:
          clientByUserId.get(link.server.userId)?.externalClientId ?? '',
        serverId: link.serverId,
        domain: link.server.name,
        username: link.server.joinSlug,
        status: this.mapLinkStatusToWhmcs(link.status, link.server.status),
        planSlug: link.server.plan?.slug ?? 'unknown',
        ramMb: link.server.ramMb,
        gameCloudStatus: link.server.status,
        linkStatus: link.status,
        lastReconciledAt: link.lastReconciledAt?.toISOString() ?? null,
      })),
    };
  }

  async importService(
    installation: InstallationRef,
    input: WhmcsImportServiceRequest,
    idempotencyKey: string,
  ): Promise<{
    externalServiceId: string;
    status: string;
    serverId: string;
    imported: boolean;
  }> {
    const existingOp = await this.prisma.integrationOperation.findUnique({
      where: { idempotencyKey },
    });
    if (existingOp?.result) {
      return existingOp.result as {
        externalServiceId: string;
        status: string;
        serverId: string;
        imported: boolean;
      };
    }

    const existing = await this.prisma.whmcsServiceLink.findUnique({
      where: {
        installationId_externalServiceId: {
          installationId: installation.id,
          externalServiceId: input.externalServiceId,
        },
      },
    });

    if (existing) {
      throw new ConflictException('Service link already exists');
    }

    const clientLink = await this.prisma.whmcsClientLink.findUnique({
      where: {
        installationId_externalClientId: {
          installationId: installation.id,
          externalClientId: input.externalClientId,
        },
      },
    });

    if (!clientLink) {
      throw new NotFoundException(`Unknown WHMCS client ${input.externalClientId}`);
    }

    const server = await this.prisma.gameServer.findUnique({
      where: { id: input.serverId },
    });

    if (!server || server.userId !== clientLink.userId) {
      throw new NotFoundException('Server not found for client');
    }

    const existingServerLink = await this.prisma.whmcsServiceLink.findUnique({
      where: { serverId: server.id },
    });

    if (existingServerLink) {
      throw new ConflictException('Server is already linked to a WHMCS service');
    }

    const metadata =
      input.expectedPlanSlug !== undefined
        ? ({ expectedPlanSlug: input.expectedPlanSlug } satisfies Record<string, string>)
        : undefined;

    const link = await this.prisma.whmcsServiceLink.create({
      data: {
        installationId: installation.id,
        externalServiceId: input.externalServiceId,
        externalProductId: input.externalProductId,
        serverId: server.id,
        status: server.billingSuspendedAt ? 'SUSPENDED' : 'ACTIVE',
        metadata: metadata as Prisma.InputJsonValue | undefined,
      },
      include: { server: { include: { plan: true } } },
    });

    const result = {
      externalServiceId: link.externalServiceId,
      status: link.status,
      serverId: link.serverId,
      imported: true,
    };

    await this.prisma.integrationOperation.create({
      data: {
        installationId: installation.id,
        idempotencyKey,
        operation: 'service:import',
        externalRef: input.externalServiceId,
        status: 'COMPLETED',
        result: result as object,
      },
    });

    return result;
  }

  async reconcileInstallation(
    installation: InstallationRef,
    input: WhmcsReconcileRequest,
    externalServiceIds?: string[],
  ): Promise<WhmcsReconcileResponse> {
    const runId = randomUUID();
    const issues: IssueDraft[] = [];
    const repaired: string[] = [];

    const links = await this.prisma.whmcsServiceLink.findMany({
      where: {
        installationId: installation.id,
        ...(externalServiceIds?.length
          ? { externalServiceId: { in: externalServiceIds } }
          : {}),
      },
      include: {
        server: { include: { plan: true } },
      },
    });

    for (const link of links) {
      issues.push(
        ...this.inspectServiceLink(link, input),
      );
    }

    if (!externalServiceIds?.length) {
      issues.push(...(await this.findOrphanServers(installation.id)));
    }

    if (input.autoRepair && !input.dryRun) {
      for (const link of links) {
        const repairedCodes = await this.applySafeRepairs(link, input);
        repaired.push(...repairedCodes);
      }
    }

    if (!input.dryRun) {
      const persistedIssues = await Promise.all(
        issues.map((issue) =>
          this.prisma.reconciliationIssue.create({
            data: {
              installationId: installation.id,
              runId,
              externalServiceId: issue.externalServiceId,
              serverId: issue.serverId,
              code: issue.code,
              severity: issue.severity,
              message: issue.message,
              details: issue.details as Prisma.InputJsonValue | undefined,
              autoRepaired: repaired.some((code) => code === issue.code),
            },
          }),
        ),
      );

      await this.prisma.whmcsServiceLink.updateMany({
        where: {
          installationId: installation.id,
          id: { in: links.map((link) => link.id) },
        },
        data: { lastReconciledAt: new Date() },
      });

      const blockingIssues = issues.filter(
        (issue) => issue.severity === 'BLOCKING' || issue.severity === 'SECURITY',
      );

      if (blockingIssues.length > 0) {
        await this.events.emitEvent(
          installation.id,
          'reconciliation_required',
          {
            runId,
            issueCount: issues.length,
            blockingCount: blockingIssues.length,
          },
          `reconciliation:${installation.id}:${runId}`,
        );
      }

      return {
        runId,
        dryRun: false,
        issues: persistedIssues.map((issue) => ({
          id: issue.id,
          code: issue.code,
          severity: issue.severity,
          message: issue.message,
          externalServiceId: issue.externalServiceId,
          serverId: issue.serverId,
          details: issue.details as Record<string, unknown> | undefined,
          autoRepaired: issue.autoRepaired,
        })),
        repaired,
      };
    }

    return {
      runId,
      dryRun: true,
      issues: issues.map((issue) => ({
        id: randomUUID(),
        code: issue.code,
        severity: issue.severity,
        message: issue.message,
        externalServiceId: issue.externalServiceId ?? null,
        serverId: issue.serverId ?? null,
        details: issue.details,
        autoRepaired: false,
      })),
      repaired: [],
    };
  }

  async reconcileService(
    installation: InstallationRef,
    externalServiceId: string,
    input: WhmcsReconcileRequest,
  ): Promise<WhmcsReconcileResponse> {
    const link = await this.prisma.whmcsServiceLink.findUnique({
      where: {
        installationId_externalServiceId: {
          installationId: installation.id,
          externalServiceId,
        },
      },
    });

    if (!link) {
      throw new NotFoundException(`Unknown WHMCS service ${externalServiceId}`);
    }

    return this.reconcileInstallation(installation, input, [externalServiceId]);
  }

  private inspectServiceLink(
    link: {
      externalServiceId: string;
      status: string;
      metadata: unknown;
      server: {
        id: string;
        status: string;
        ramMb: number;
        billingSuspendedAt: Date | null;
        joinSlug: string | null;
        edition: string;
        plan: { slug: string; maxRamMb: number } | null;
      };
    },
    input: WhmcsReconcileRequest,
  ): IssueDraft[] {
    const issues: IssueDraft[] = [];
    const server = link.server;
    const base = {
      externalServiceId: link.externalServiceId,
      serverId: server.id,
    };

    if (link.status === 'SUSPENDED' && !server.billingSuspendedAt) {
      issues.push({
        ...base,
        code: 'status_mismatch_suspended',
        severity: 'BLOCKING',
        message: 'WHMCS link is suspended but server is not billing-suspended',
      });
    }

    if (link.status === 'ACTIVE' && server.billingSuspendedAt) {
      issues.push({
        ...base,
        code: 'status_mismatch_active',
        severity: 'WARNING',
        message: 'WHMCS link is active but server billing suspension is set',
      });
    }

    if (link.status === 'TERMINATED' && server.status !== 'DELETED') {
      issues.push({
        ...base,
        code: 'status_mismatch_terminated',
        severity: 'DESTRUCTIVE',
        message: 'WHMCS link is terminated but GameCloud server still exists',
        details: { gameCloudStatus: server.status },
      });
    }

    if (
      link.status === 'SUSPENDED' &&
      server.status === 'RUNNING' &&
      !server.billingSuspendedAt
    ) {
      issues.push({
        ...base,
        code: 'startable_while_suspended',
        severity: 'SECURITY',
        message: 'Suspended service is still running without billing suspension',
      });
    }

    const metadata = link.metadata as { expectedPlanSlug?: string; expectedRamMb?: number } | null;
    const expectedPlanSlug = input.expectedPlanSlug ?? metadata?.expectedPlanSlug;
    if (expectedPlanSlug && server.plan && expectedPlanSlug !== server.plan.slug) {
      issues.push({
        ...base,
        code: 'plan_mismatch',
        severity: 'WARNING',
        message: `Expected plan ${expectedPlanSlug} but server has ${server.plan.slug}`,
        details: { expectedPlanSlug, actualPlanSlug: server.plan.slug },
      });
    }

    const expectedRamMb = input.expectedRamMb ?? metadata?.expectedRamMb;
    if (expectedRamMb && expectedRamMb !== server.ramMb) {
      issues.push({
        ...base,
        code: 'ram_mismatch',
        severity: 'WARNING',
        message: `Expected RAM ${expectedRamMb} MiB but server has ${server.ramMb} MiB`,
        details: { expectedRamMb, actualRamMb: server.ramMb },
      });
    }

    if (input.expectedWhmcsStatus) {
      const expectedLinkStatus = this.mapWhmcsStatusToLink(input.expectedWhmcsStatus);
      if (expectedLinkStatus && link.status !== expectedLinkStatus) {
        issues.push({
          ...base,
          code: 'whmcs_status_mismatch',
          severity: 'BLOCKING',
          message: `WHMCS reports ${input.expectedWhmcsStatus} but link status is ${link.status}`,
          details: {
            expectedWhmcsStatus: input.expectedWhmcsStatus,
            linkStatus: link.status,
          },
        });
      }
    }

    if (!server.joinSlug) {
      issues.push({
        ...base,
        code: 'missing_join_slug',
        severity: 'INFO',
        message: 'Server has no join slug assigned',
      });
    } else {
      const hostname = formatJoinAddress(server.joinSlug, server.edition as 'JAVA' | 'BEDROCK');
      issues.push({
        ...base,
        code: 'join_hostname',
        severity: 'INFO',
        message: `Join hostname ${hostname}`,
        details: { joinHostname: hostname },
      });
    }

    return issues;
  }

  private async findOrphanServers(installationId: string): Promise<IssueDraft[]> {
    const clientUserIds = await this.prisma.whmcsClientLink.findMany({
      where: { installationId },
      select: { userId: true },
    });
    const userIds = clientUserIds.map((link) => link.userId);

    if (userIds.length === 0) {
      return [];
    }

    const linkedServerIds = await this.prisma.whmcsServiceLink.findMany({
      where: { installationId },
      select: { serverId: true },
    });
    const linkedSet = new Set(linkedServerIds.map((link) => link.serverId));

    const orphans = await this.prisma.gameServer.findMany({
      where: {
        userId: { in: userIds },
        status: { not: 'DELETED' },
        id: { notIn: [...linkedSet] },
      },
      select: { id: true, name: true, userId: true },
    });

    return orphans.map((server) => ({
      code: 'orphan_server',
      severity: 'WARNING' as const,
      message: `GameCloud server ${server.name} has no WHMCS service link`,
      serverId: server.id,
      details: { userId: server.userId },
    }));
  }

  private async applySafeRepairs(
    link: {
      id: string;
      status: string;
      serverId: string;
      server: {
        billingSuspendedAt: Date | null;
        status: string;
      };
    },
    input: WhmcsReconcileRequest,
  ): Promise<string[]> {
    const repaired: string[] = [];

    if (link.status === 'SUSPENDED' && !link.server.billingSuspendedAt) {
      await this.prisma.gameServer.update({
        where: { id: link.serverId },
        data: {
          billingSuspendedAt: new Date(),
          billingSuspendReason: 'reconciliation-auto-repair',
        },
      });
      repaired.push('status_mismatch_suspended');
    }

    if (
      link.status === 'ACTIVE' &&
      link.server.billingSuspendedAt &&
      input.expectedWhmcsStatus === 'Active'
    ) {
      await this.prisma.gameServer.update({
        where: { id: link.serverId },
        data: {
          billingSuspendedAt: null,
          billingSuspendReason: null,
        },
      });
      repaired.push('status_mismatch_active');
    }

    if (
      link.status === 'SUSPENDED' &&
      link.server.status === 'RUNNING' &&
      !link.server.billingSuspendedAt
    ) {
      await this.prisma.gameServer.update({
        where: { id: link.serverId },
        data: {
          billingSuspendedAt: new Date(),
          billingSuspendReason: 'reconciliation-security-repair',
        },
      });
      repaired.push('startable_while_suspended');
    }

    return repaired;
  }

  private mapLinkStatusToWhmcs(
    linkStatus: string,
    serverStatus: string,
  ): 'Active' | 'Suspended' | 'Terminated' | 'Pending' {
    if (linkStatus === 'TERMINATED' || serverStatus === 'DELETED') {
      return 'Terminated';
    }
    if (linkStatus === 'SUSPENDED') {
      return 'Suspended';
    }
    if (linkStatus === 'PENDING') {
      return 'Pending';
    }
    return 'Active';
  }

  private mapWhmcsStatusToLink(
    status: string,
  ): 'PENDING' | 'ACTIVE' | 'SUSPENDED' | 'TERMINATED' | null {
    switch (status) {
      case 'Pending':
        return 'PENDING';
      case 'Active':
        return 'ACTIVE';
      case 'Suspended':
      case 'Cancelled':
      case 'Fraud':
        return 'SUSPENDED';
      case 'Terminated':
        return 'TERMINATED';
      default:
        return null;
    }
  }
}
