import {
  Injectable,
  NotFoundException,
  ConflictException,
  BadRequestException,
  Logger,
} from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { PrismaService } from '../../../prisma/prisma.service';
import { EncryptionService } from '../../../common/services/encryption.service';
import { CreateCredentialDto } from './dto/create-credential.dto';
import { UpdateCredentialDto } from './dto/update-credential.dto';
import { QueryCredentialsDto } from './dto/query-credentials.dto';
import { IntegrationType, SyncStatus, Prisma } from '@prisma/client';

/**
 * Response type for credential listing (without decrypted secrets)
 */
export interface CredentialListItem {
  id: string;
  type: IntegrationType;
  name: string;
  isActive: boolean;
  lastUsed: Date | null;
  lastSync: Date | null;
  syncStatus: SyncStatus;
  syncError: string | null;
  createdAt: Date;
  updatedAt: Date;
  createdBy: {
    id: string;
    firstName: string;
    lastName: string;
    email: string;
  };
}

/**
 * Response type for single credential with decrypted credentials
 */
export interface CredentialDetail extends CredentialListItem {
  credentials: Record<string, unknown>;
}

/**
 * Paginated response wrapper
 */
export interface PaginatedCredentials {
  data: CredentialListItem[];
  meta: {
    total: number;
    page: number;
    limit: number;
    totalPages: number;
  };
}

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly encryptionService: EncryptionService,
    private readonly eventEmitter: EventEmitter2,
  ) {}

  /**
   * Creates a new integration credential with encrypted secrets
   */
  async create(
    createDto: CreateCredentialDto,
    userId: string,
  ): Promise<CredentialListItem> {
    // Check for duplicate name within the same type
    const existing = await this.prisma.integrationCredential.findUnique({
      where: {
        type_name: {
          type: createDto.type,
          name: createDto.name,
        },
      },
    });

    if (existing) {
      throw new ConflictException(
        `A credential with name "${createDto.name}" already exists for type ${createDto.type}`,
      );
    }

    // Encrypt the credentials
    const encryptedCredentials = this.encryptionService.encryptObject(
      createDto.credentials,
    );

    const credential = await this.prisma.integrationCredential.create({
      data: {
        type: createDto.type,
        name: createDto.name,
        credentials: encryptedCredentials,
        createdById: userId,
      },
      include: {
        createdBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
          },
        },
      },
    });

    this.logger.log(
      `Created credential ${credential.id} (${createDto.type}:${createDto.name}) by user ${userId}`,
    );

    this.eventEmitter.emit('credentials.changed', { type: credential.type });

    return this.mapToListItem(credential);
  }

  /**
   * Retrieves all credentials with pagination and filtering
   * Note: Does not include decrypted credentials
   */
  async findAll(query: QueryCredentialsDto): Promise<PaginatedCredentials> {
    const { type, isActive, syncStatus, search, page = 1, limit = 10 } = query;

    const where: Prisma.IntegrationCredentialWhereInput = {};

    if (type) {
      where.type = type;
    }

    if (isActive !== undefined) {
      where.isActive = isActive;
    }

    if (syncStatus) {
      where.syncStatus = syncStatus;
    }

    if (search) {
      where.name = {
        contains: search,
        mode: 'insensitive',
      };
    }

    const [credentials, total] = await Promise.all([
      this.prisma.integrationCredential.findMany({
        where,
        include: {
          createdBy: {
            select: {
              id: true,
              firstName: true,
              lastName: true,
              email: true,
            },
          },
        },
        orderBy: [{ type: 'asc' }, { name: 'asc' }],
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.integrationCredential.count({ where }),
    ]);

    return {
      data: credentials.map((c) => this.mapToListItem(c)),
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  /**
   * Retrieves a single credential by ID with decrypted credentials
   */
  async findOne(id: string): Promise<CredentialDetail> {
    const credential = await this.prisma.integrationCredential.findUnique({
      where: { id },
      include: {
        createdBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
          },
        },
      },
    });

    if (!credential) {
      throw new NotFoundException(`Credential with ID ${id} not found`);
    }

    // Decrypt credentials
    let decryptedCredentials: Record<string, unknown>;
    try {
      decryptedCredentials = this.encryptionService.decryptObject(
        credential.credentials,
      );
    } catch (error) {
      this.logger.error(`Failed to decrypt credentials for ${id}`);
      throw new BadRequestException('Failed to decrypt credentials');
    }

    // Update last used timestamp
    await this.prisma.integrationCredential.update({
      where: { id },
      data: { lastUsed: new Date() },
    });

    return {
      ...this.mapToListItem(credential),
      credentials: decryptedCredentials,
    };
  }

  /**
   * Updates an existing credential
   */
  async update(
    id: string,
    updateDto: UpdateCredentialDto,
    userId: string,
  ): Promise<CredentialListItem> {
    const existing = await this.prisma.integrationCredential.findUnique({
      where: { id },
    });

    if (!existing) {
      throw new NotFoundException(`Credential with ID ${id} not found`);
    }

    // Check for name conflict if name is being updated
    if (updateDto.name && updateDto.name !== existing.name) {
      const duplicate = await this.prisma.integrationCredential.findUnique({
        where: {
          type_name: {
            type: existing.type,
            name: updateDto.name,
          },
        },
      });

      if (duplicate) {
        throw new ConflictException(
          `A credential with name "${updateDto.name}" already exists for type ${existing.type}`,
        );
      }
    }

    // Prepare update data
    const updateData: Prisma.IntegrationCredentialUpdateInput = {};

    if (updateDto.name !== undefined) {
      updateData.name = updateDto.name;
    }

    if (updateDto.isActive !== undefined) {
      updateData.isActive = updateDto.isActive;
    }

    if (updateDto.credentials) {
      // Encrypt new credentials
      updateData.credentials = this.encryptionService.encryptObject(
        updateDto.credentials,
      );
    }

    const updated = await this.prisma.integrationCredential.update({
      where: { id },
      data: updateData,
      include: {
        createdBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
          },
        },
      },
    });

    this.logger.log(`Updated credential ${id} by user ${userId}`);

    this.eventEmitter.emit('credentials.changed', { type: updated.type });

    return this.mapToListItem(updated);
  }

  /**
   * Soft-deletes a credential by deactivating it
   */
  async remove(id: string, userId: string): Promise<CredentialListItem> {
    const existing = await this.prisma.integrationCredential.findUnique({
      where: { id },
    });

    if (!existing) {
      throw new NotFoundException(`Credential with ID ${id} not found`);
    }

    const updated = await this.prisma.integrationCredential.update({
      where: { id },
      data: { isActive: false },
      include: {
        createdBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
          },
        },
      },
    });

    this.logger.log(`Deactivated credential ${id} by user ${userId}`);

    this.eventEmitter.emit('credentials.changed', { type: updated.type });

    return this.mapToListItem(updated);
  }

  /**
   * Tests a credential connection
   */
  async testConnection(
    id: string,
  ): Promise<{ success: boolean; message: string; latency?: number }> {
    const credential = await this.findOne(id);

    if (!credential.isActive) {
      return {
        success: false,
        message: 'Credential is deactivated',
      };
    }

    const startTime = Date.now();

    try {
      // Integration-specific connection tests
      const result = await this.performConnectionTest(
        credential.type,
        credential.credentials,
      );

      const latency = Date.now() - startTime;

      // Update sync status on success
      await this.updateSyncStatus(id, SyncStatus.SUCCESS, null);

      return {
        success: true,
        message: result.message || 'Connection successful',
        latency,
      };
    } catch (error) {
      const errorMessage =
        error instanceof Error ? error.message : 'Connection test failed';

      // Update sync status on error
      await this.updateSyncStatus(id, SyncStatus.ERROR, errorMessage);

      return {
        success: false,
        message: errorMessage,
      };
    }
  }

  /**
   * Gets decrypted credentials for internal use (e.g., sync jobs)
   * Does not update lastUsed timestamp
   */
  async getCredentialsForSync(id: string): Promise<{
    type: IntegrationType;
    credentials: Record<string, unknown>;
  }> {
    const credential = await this.prisma.integrationCredential.findUnique({
      where: { id },
    });

    if (!credential) {
      throw new NotFoundException(`Credential with ID ${id} not found`);
    }

    if (!credential.isActive) {
      throw new BadRequestException(`Credential ${id} is deactivated`);
    }

    const decryptedCredentials = this.encryptionService.decryptObject<
      Record<string, unknown>
    >(credential.credentials);

    return {
      type: credential.type,
      credentials: decryptedCredentials,
    };
  }

  /**
   * Find the first active credential for a given integration type.
   * Returns decrypted credentials. Used internally by connectors.
   */
  async findActiveByType(
    type: string,
  ): Promise<{ id: string; credentials: Record<string, string> } | null> {
    const credential = await this.prisma.integrationCredential.findFirst({
      where: { type: type as IntegrationType, isActive: true },
      orderBy: { updatedAt: 'desc' },
    });

    if (!credential) return null;

    try {
      const decrypted = this.encryptionService.decryptObject(credential.credentials);
      return { id: credential.id, credentials: decrypted as Record<string, string> };
    } catch (error) {
      this.logger.error(`Failed to decrypt credentials for type ${type}: ${error}`);
      return null;
    }
  }

  /**
   * Updates the sync status of a credential
   */
  async updateSyncStatus(
    id: string,
    status: SyncStatus,
    error: string | null,
  ): Promise<void> {
    await this.prisma.integrationCredential.update({
      where: { id },
      data: {
        syncStatus: status,
        syncError: error,
        lastSync: status === SyncStatus.SUCCESS ? new Date() : undefined,
      },
    });
  }

  /**
   * Performs integration-specific connection test
   */
  private async performConnectionTest(
    type: IntegrationType,
    credentials: Record<string, unknown>,
  ): Promise<{ success: boolean; message: string }> {
    // Each integration type would have its own connection test logic
    // For now, we validate that required fields are present

    switch (type) {
      case IntegrationType.PLENTYONE:
        return this.testPlentyOneConnection(credentials);
      case IntegrationType.ZULIP:
        return this.testZulipConnection(credentials);
      case IntegrationType.TODOIST:
        return this.testTodoistConnection(credentials);
      case IntegrationType.FREESCOUT:
        return this.testFreescoutConnection(credentials);
      case IntegrationType.NEXTCLOUD:
        return this.testNextcloudConnection(credentials);
      case IntegrationType.ECODMS:
        return this.testEcodmsConnection(credentials);
      case IntegrationType.GEMBADOCS:
        return this.testGembadocsConnection(credentials);
      default:
        // For generic/custom integrations, just validate basic structure
        return { success: true, message: 'Credential format validated' };
    }
  }

  private async testPlentyOneConnection(
    credentials: Record<string, unknown>,
  ): Promise<{ success: boolean; message: string }> {
    const required = ['apiUrl', 'apiKey'];
    this.validateRequiredFields(credentials, required, 'PlentyOne');
    // In production, make an actual API call to verify credentials
    return { success: true, message: 'PlentyOne credentials validated' };
  }

  private async testZulipConnection(
    credentials: Record<string, unknown>,
  ): Promise<{ success: boolean; message: string }> {
    const required = ['zulipUrl', 'botEmail', 'apiKey'];
    this.validateRequiredFields(credentials, required, 'Zulip');
    return { success: true, message: 'Zulip credentials validated' };
  }

  private async testTodoistConnection(
    credentials: Record<string, unknown>,
  ): Promise<{ success: boolean; message: string }> {
    const required = ['apiToken'];
    this.validateRequiredFields(credentials, required, 'Todoist');
    return { success: true, message: 'Todoist credentials validated' };
  }

  private async testFreescoutConnection(
    credentials: Record<string, unknown>,
  ): Promise<{ success: boolean; message: string }> {
    const required = ['apiUrl', 'apiKey'];
    this.validateRequiredFields(credentials, required, 'FreeScout');
    return { success: true, message: 'FreeScout credentials validated' };
  }

  private async testNextcloudConnection(
    credentials: Record<string, unknown>,
  ): Promise<{ success: boolean; message: string }> {
    const required = ['serverUrl', 'username', 'password'];
    this.validateRequiredFields(credentials, required, 'Nextcloud');
    return { success: true, message: 'Nextcloud credentials validated' };
  }

  private async testEcodmsConnection(
    credentials: Record<string, unknown>,
  ): Promise<{ success: boolean; message: string }> {
    const required = ['apiUrl', 'username', 'password'];
    this.validateRequiredFields(credentials, required, 'ecoDMS');
    return { success: true, message: 'ecoDMS credentials validated' };
  }

  private async testGembadocsConnection(
    credentials: Record<string, unknown>,
  ): Promise<{ success: boolean; message: string }> {
    const required = ['apiUrl', 'apiKey'];
    this.validateRequiredFields(credentials, required, 'GembaDocs');
    return { success: true, message: 'GembaDocs credentials validated' };
  }

  private validateRequiredFields(
    credentials: Record<string, unknown>,
    required: string[],
    integrationName: string,
  ): void {
    const missing = required.filter(
      (field) => !credentials[field] || credentials[field] === '',
    );

    if (missing.length > 0) {
      throw new BadRequestException(
        `${integrationName}: Missing required fields: ${missing.join(', ')}`,
      );
    }
  }

  /**
   * Maps a Prisma credential to a list item (without secrets)
   */
  private mapToListItem(credential: any): CredentialListItem {
    return {
      id: credential.id,
      type: credential.type,
      name: credential.name,
      isActive: credential.isActive,
      lastUsed: credential.lastUsed,
      lastSync: credential.lastSync,
      syncStatus: credential.syncStatus,
      syncError: credential.syncError,
      createdAt: credential.createdAt,
      updatedAt: credential.updatedAt,
      createdBy: credential.createdBy,
    };
  }
}
