/**
 * Federation Service
 *
 * Manages instance identity and federation connections.
 */

import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Instance, Prisma } from "@prisma/client";
import { generateKeyPairSync } from "crypto";
import { randomUUID } from "crypto";
import { PrismaService } from "../prisma/prisma.service";
import { CryptoService } from "./crypto.service";
import {
  InstanceIdentity,
  PublicInstanceIdentity,
  KeyPair,
  FederationCapabilities,
} from "./types/instance.types";

@Injectable()
export class FederationService {
  private readonly logger = new Logger(FederationService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly config: ConfigService,
    private readonly crypto: CryptoService
  ) {}

  /**
   * Get the instance identity, creating it if it doesn't exist
   */
  async getInstanceIdentity(): Promise<InstanceIdentity> {
    // Try to find existing instance
    let instance = await this.prisma.instance.findFirst();

    if (!instance) {
      this.logger.log("No instance identity found, creating new one");
      instance = await this.createInstanceIdentity();
    }

    return this.mapToInstanceIdentity(instance);
  }

  /**
   * Get public instance identity (without private key)
   */
  async getPublicIdentity(): Promise<PublicInstanceIdentity> {
    const instance = await this.getInstanceIdentity();

    // Exclude private key from public identity
    const { privateKey: _privateKey, ...publicIdentity } = instance;

    return publicIdentity;
  }

  /**
   * Generate a new RSA key pair for instance signing
   * Uses RSA-4096 for future-proof security (NIST recommendation)
   */
  generateKeypair(): KeyPair {
    const { publicKey, privateKey } = generateKeyPairSync("rsa", {
      modulusLength: 4096,
      publicKeyEncoding: {
        type: "spki",
        format: "pem",
      },
      privateKeyEncoding: {
        type: "pkcs8",
        format: "pem",
      },
    });

    return {
      publicKey,
      privateKey,
    };
  }

  /**
   * Regenerate the instance's keypair
   * Returns public identity only (no private key exposure)
   */
  async regenerateKeypair(): Promise<PublicInstanceIdentity> {
    const instance = await this.getInstanceIdentity();
    const { publicKey, privateKey } = this.generateKeypair();

    // Encrypt private key before storing
    const encryptedPrivateKey = this.crypto.encrypt(privateKey);

    const updatedInstance = await this.prisma.instance.update({
      where: { id: instance.id },
      data: {
        publicKey,
        privateKey: encryptedPrivateKey,
      },
    });

    this.logger.log("Instance keypair regenerated");

    // Return public identity only (security fix)
    const identity = this.mapToInstanceIdentity(updatedInstance);
    const { privateKey: _privateKey, ...publicIdentity } = identity;
    return publicIdentity;
  }

  /**
   * Update instance configuration
   * Allows updating name, capabilities, and metadata
   * Returns public identity only (no private key exposure)
   */
  async updateInstanceConfiguration(updates: {
    name?: string;
    capabilities?: FederationCapabilities;
    metadata?: Record<string, unknown>;
  }): Promise<PublicInstanceIdentity> {
    const instance = await this.getInstanceIdentity();

    // Build update data object
    const data: Prisma.InstanceUpdateInput = {};

    if (updates.name !== undefined) {
      data.name = updates.name;
    }

    if (updates.capabilities !== undefined) {
      data.capabilities = updates.capabilities as Prisma.JsonObject;
    }

    if (updates.metadata !== undefined) {
      data.metadata = updates.metadata as Prisma.JsonObject;
    }

    const updatedInstance = await this.prisma.instance.update({
      where: { id: instance.id },
      data,
    });

    this.logger.log(`Instance configuration updated: ${JSON.stringify(updates)}`);

    // Return public identity only (security fix)
    const identity = this.mapToInstanceIdentity(updatedInstance);
    const { privateKey: _privateKey, ...publicIdentity } = identity;
    return publicIdentity;
  }

  /**
   * Create a new instance identity
   */
  private async createInstanceIdentity(): Promise<Instance> {
    const { publicKey, privateKey } = this.generateKeypair();

    const instanceId = this.generateInstanceId();
    const name = this.config.get<string>("INSTANCE_NAME") ?? "Mosaic Instance";
    const url = this.config.get<string>("INSTANCE_URL") ?? "http://localhost:3000";

    // Validate instance URL
    this.validateInstanceUrl(url);

    const capabilities: FederationCapabilities = {
      supportsQuery: true,
      supportsCommand: true,
      supportsEvent: true,
      supportsAgentSpawn: true,
      protocolVersion: "1.0",
    };

    // Encrypt private key before storing (AES-256-GCM)
    const encryptedPrivateKey = this.crypto.encrypt(privateKey);

    const instance = await this.prisma.instance.create({
      data: {
        instanceId,
        name,
        url,
        publicKey,
        privateKey: encryptedPrivateKey,
        capabilities: capabilities as Prisma.JsonObject,
        metadata: {},
      },
    });

    this.logger.log(`Created instance identity: ${instanceId}`);

    return instance;
  }

  /**
   * Get a federation connection by remote instance ID
   * Returns the first active or pending connection
   */
  async getConnectionByRemoteInstanceId(
    remoteInstanceId: string
  ): Promise<{ remotePublicKey: string } | null> {
    const connection = await this.prisma.federationConnection.findFirst({
      where: {
        remoteInstanceId,
        status: {
          in: ["ACTIVE", "PENDING"],
        },
      },
      select: {
        remotePublicKey: true,
      },
    });

    return connection;
  }

  /**
   * Generate a unique instance ID
   */
  private generateInstanceId(): string {
    return `instance-${randomUUID()}`;
  }

  /**
   * Validate instance URL format
   */
  private validateInstanceUrl(url: string): void {
    try {
      const parsedUrl = new URL(url);
      if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
        throw new Error("URL must use HTTP or HTTPS protocol");
      }
    } catch {
      throw new Error(`Invalid INSTANCE_URL: ${url}. Must be a valid HTTP/HTTPS URL.`);
    }
  }

  /**
   * Map Prisma Instance to InstanceIdentity type
   * Decrypts private key from storage
   */
  private mapToInstanceIdentity(instance: Instance): InstanceIdentity {
    // Decrypt private key (stored as AES-256-GCM encrypted)
    const decryptedPrivateKey = this.crypto.decrypt(instance.privateKey);

    return {
      id: instance.id,
      instanceId: instance.instanceId,
      name: instance.name,
      url: instance.url,
      publicKey: instance.publicKey,
      privateKey: decryptedPrivateKey,
      capabilities: instance.capabilities as FederationCapabilities,
      metadata: instance.metadata as Record<string, unknown>,
      createdAt: instance.createdAt,
      updatedAt: instance.updatedAt,
    };
  }
}
