import {
  BadRequestException,
  ConflictException,
  forwardRef,
  Inject,
  Injectable,
  InternalServerErrorException,
  Logger,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ClientEntity } from './entities/client.entity';
import { Repository } from 'typeorm';
import { BaseClientService } from './abstracts/client.abstract';
import { ClientResponseDto } from './dto/client-response.dto';
import { CreateClientDto } from './dto/create-client.dto';
import { PaginatedClientDto } from './dto/paginated-client.dto';
import { PlanResponseDto } from './dto/plan.dto';
import { UpdateClientDto } from './dto/update-client.dto';
import { PlanDataDto } from './dto/plan-data.dto';
import { UserService } from 'modules/user/users.service';
import { RoleService } from 'modules/role-permission/roles/role.service';
import { PaginatedResponseDto } from 'common/utils/paginated-response.dto';
import { mapUsersToResponseDtos, mapUserToResponseDto, UserResponseDto } from 'modules/user/dto/user-response.dto';
import * as nodemailer from 'nodemailer';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class ClientService extends BaseClientService {
  private readonly logger = new Logger(ClientService.name);
  private transporter: nodemailer.Transporter;

  constructor(
    @InjectRepository(ClientEntity)
    private readonly clientRepository: Repository<ClientEntity>,

    private readonly userSevice: UserService,
    @Inject(forwardRef(() => RoleService))
    private readonly roleService: RoleService,
    private readonly configService: ConfigService,
  ) {
    super();
    this.initializeEmailTransporter();
  }

  private initializeEmailTransporter(): void {
    try {
      const smtpConfig = {
        host: this.configService.get<string>('AUTH_SMTP_HOST'),
        port: this.configService.get<number>('AUTH_SMTP_PORT'),
        secure: true,
        auth: {
          user: this.configService.get<string>('AUTH_SMTP_USER'),
          pass: this.configService.get<string>('AUTH_SMTP_PASSWORD'),
        },
        tls: {
          rejectUnauthorized: false,
        },
        // Add connection timeout and other robust settings
        connectionTimeout: 60000, // 60 seconds
        greetingTimeout: 30000, // 30 seconds
        socketTimeout: 60000, // 60 seconds
        // Add pool settings for better connection management
        pool: true,
        maxConnections: 5,
        maxMessages: 100,
        // Add retry settings
        retryDelay: 1000, // 1 second
        maxRetries: 3,
      };

      this.transporter = nodemailer.createTransport(smtpConfig);

      // Verify the connection configuration
      this.verifyEmailConnection();

      this.logger.debug('Email transporter initialized successfully');
    } catch (error) {
      this.logger.error('Failed to initialize email transporter:', error);
      // Don't throw error here - let the service start but log the issue
      this.logger.warn('Email service will be disabled due to configuration error');
    }
  }

  private async verifyEmailConnection(): Promise<void> {
    try {
      await this.transporter.verify();
      this.logger.debug('Email transporter connection verified successfully');
    } catch (error) {
      this.logger.error('Email transporter verification failed:', error);
      this.logger.warn('Email functionality may not work properly');
    }
  }

  async createClient(createClientDto: CreateClientDto): Promise<ClientResponseDto> {
    this.logger.log(`Creating client with data: ${JSON.stringify(createClientDto)}`);

    try {
      // Check if client with email already exists
      const existingClient = await this.clientRepository.findOne({
        where: { email: createClientDto.email },
      });

      if (existingClient) {
        throw new ConflictException('Client with this email already exists');
      }

      const newClient = this.clientRepository.create(createClientDto);
      const savedClient = await this.clientRepository.save(newClient);

      this.logger.log(`Client created with ID: ${savedClient.id}`);
      return this.mapToClientResponseDto(savedClient);
    } catch (error) {
      this.logger.error(`Error creating client: ${error.message}`, error.stack);
      throw error;
    }
  }

  async getAllClients(page: number = 1, limit: number = 10): Promise<PaginatedClientDto> {
    this.logger.log(`Fetching clients with pagination - page: ${page}, limit: ${limit}`);

    try {
      const skip = (page - 1) * limit;
      const [clients, total] = await this.clientRepository.findAndCount({
        // relations: [],
        skip,
        take: limit,
        order: { createdAt: 'DESC' },
      });

      const clientDtos = clients.map(client => this.mapToClientResponseDto(client));

      return {
        data: clientDtos,
        total,
        limit,
        currentPage: page,
        totalPages: Math.ceil(total / limit),
      };
    } catch (error) {
      this.logger.error(`Error fetching clients: ${error.message}`, error.stack);
      throw error;
    }
  }

  async getClientById(clientId: string): Promise<ClientResponseDto> {
    this.logger.log(`Fetching client with ID: ${clientId}`);

    try {
      const client = await this.clientRepository.findOne({
        where: { id: clientId },
        relations: ['linkedUsers', 'roles'],
      });

      if (!client) {
        throw new NotFoundException(`Client with ID ${clientId} not found`);
      }

      return this.mapToClientResponseDto(client);
    } catch (error) {
      this.logger.error(`Error fetching client: ${error.message}`, error.stack);
      throw error;
    }
  }

  //  NOTE: This method is used to fetch the full client entity, including relations.
  // It is useful for internal operations where full client details are required.
  async getClientEntityById(clientId: string): Promise<ClientEntity> {
    this.logger.log(`Fetching client entity with ID: ${clientId}`);
    try {
      const client = await this.clientRepository.findOne({
        where: { id: clientId },
        relations: ['linkedUsers', 'roles'],
      });

      if (!client) {
        throw new NotFoundException(`Client with ID ${clientId} not found`);
      }

      return client;
    } catch (error) {
      this.logger.error(`Error fetching client entity: ${error.message}`, error.stack);
      throw new BadRequestException(`Failed to fetch client entity: ${error.message}`);
    }
  }

  async getClientPlan(clientId: string): Promise<PlanResponseDto> {
    this.logger.log(`Fetching plan for client ID: ${clientId}`);

    try {
      const client = await this.clientRepository.findOne({
        where: { id: clientId },
        select: ['id', 'plan', 'maxUsers'],
      });

      if (!client) {
        throw new NotFoundException(`Client with ID ${clientId} not found`);
      }

      return {
        clientId: client.id,
        plan: client.plan,
        maxUsers: client.maxUsers,
      };
    } catch (error) {
      this.logger.error(`Error fetching client plan: ${error.message}`, error.stack);
      throw new BadRequestException(`Failed to fetch client plan: ${error.message}`);
    }
  }

  async deleteClient(clientId: string): Promise<boolean> {
    this.logger.log(`Deleting client with ID: ${clientId}`);

    try {
      const client = await this.clientRepository.findOne({
        where: { id: clientId },
        relations: ['linkedUsers'],
      });

      if (!client) {
        throw new NotFoundException(`Client with ID ${clientId} not found`);
      }

      // Check if client has linked users
      if (client.linkedUsers && client.linkedUsers.length > 0) {
        throw new BadRequestException('Cannot delete client with linked users. Please unlink all users first.');
      }

      const result = await this.clientRepository.delete(clientId);
      const success = result.affected > 0;

      if (success) {
        this.logger.log(`Client with ID ${clientId} deleted successfully`);
      }

      return success;
    } catch (error) {
      this.logger.error(`Error deleting client: ${error.message}`, error.stack);
      throw new BadRequestException(`Failed to delete client: ${error.message}`);
    }
  }

  async getClientUsers(clientId: string, page: number = 1, limit: number = 10): Promise<PaginatedResponseDto<UserResponseDto>> {
    this.logger.log(`Fetching users for client ID: ${clientId}, page: ${page}, limit: ${limit}`);

    try {
      const client = await this.clientRepository.findOne({
        where: { id: clientId },
      });

      if (!client) {
        throw new NotFoundException(`Client with ID ${clientId} not found`);
      }
      const skip = (page - 1) * limit;
      const findClient = await this.clientRepository.find({
        where: { id: clientId },
        relations: ['linkedUsers'],
        skip,
        take: limit,
      });
      this.logger.log(`Found ${findClient.length} users for client ID ${clientId}`);
      const users = mapUsersToResponseDtos(findClient[0]?.linkedUsers || []);
      const totalUsers = findClient[0]?.linkedUsers.length || 0;
      return {
        items: users,
        totalItems: totalUsers,
        itemsPerPage: limit,
        currentPage: page,
        totalPages: Math.ceil(totalUsers / limit),
      };
    } catch (error) {
      this.logger.error(`Error fetching client users: ${error.message}`, error.stack);
      throw new BadRequestException(`Failed to fetch client users: ${error.message}`);
    }
  }

  async unlinkUserFromClient(clientId: string, userId: number): Promise<boolean> {
    this.logger.log(`Unlinking user ${userId} from client ${clientId}`);

    try {
      const client = await this.clientRepository.findOne({
        where: { id: clientId },
        relations: ['linkedUsers', 'roles'],
      });
      // this.logger.log('Client found:', client);
      // this.logger.log('Client linked users:', client.roles);
      if (!client) {
        throw new NotFoundException(`Client with ID ${clientId} not found`);
      }
      const user = await this.userSevice.findUserByIdWithRoles(userId);
      this.logger.log('User found:', user);
      if (!user) {
        throw new NotFoundException(`User with ID ${userId} not found`);
      }

      // Check if user is linked to the client
      this.logger.log(`Checking if user ${userId} is linked to client ${clientId}`);
      this.logger.log(`User linkedClientId: ${user.linkedClientId}, Client ID: ${clientId}`);
      // ✅ Remove only roles that match the client ID
      const rolesToRemove = user.roles.filter(role => role.clientId === clientId);
      const roleIdsToRemove = rolesToRemove.map(role => role.id);

      if (roleIdsToRemove.length > 0) {
        this.logger.log(`Removing roles [${roleIdsToRemove}] from user ${userId} for client ${clientId}`);
        roleIdsToRemove.forEach(async roleId => {
          this.logger.log(`Removing role ${roleId} from user ${userId}`);
          await this.roleService.removeUserFromRole(userId, roleId);
        });
      }

      // ✅ Unlink user
      await this.userSevice.unlinkUserFromClient(userId, clientId);

      this.logger.log(`User ${userId} successfully unlinked from client ${clientId}`);
      return true;
    } catch (error) {
      this.logger.error(`Error unlinking user from client: ${error.message}`, error.stack);
      throw new BadRequestException(`Failed to unlink user from client: ${error.message}`);
    }
  }

  private async sendEmail(to: string, subject: string, html: string): Promise<void> {
    const mailOptions = {
      from: this.configService.get<string>('AUTH_SMTP_USER'),
      to,
      subject,
      html,
    };

    try {
      await this.transporter.sendMail(mailOptions);
      this.logger.debug(`Email sent successfully to ${to}`);
    } catch (error) {
      this.logger.error(`Failed to send email to ${to}:`, error);
      throw new InternalServerErrorException('Failed to send email');
    }
  }

  // Async email sending helper to avoid blocking user creation
  private async sendLinkingEmailAsync(email: string, firstName?: string): Promise<void> {
    this.logger.debug(`Sending  email to ${email}`);
    try {
      const loginUrl = `${this.configService.get<string>('FRONTEND_URL')}/get-started`;

      const emailHtml = this.generateUserLinkedMessageTemplate(loginUrl, firstName);

      await this.sendEmail(email, 'User Linked', emailHtml);
    } catch (error) {
      this.logger.error(`Failed to send verification email to ${email}:`, error);
      throw error;
    }
  }

  // Extract email template to separate method for better maintainability
  private generateUserLinkedMessageTemplate(verificationUrl: string, firstName?: string): string {
    return `
    <html>
      <body>
        <h1>Welcome to Our Service</h1>
        <p>Dear ${firstName || 'User'},</p>
        <p>Your account has been successfully linked to a client.</p>
        <p>Please click the link below to verify your email address:</p>
        <a href="${verificationUrl}">Verify Email</a>
        <p>If you did not request this, please ignore this email.</p>
        <p>Thank you!</p>
      </body>
    </html>
      `;
  }
  async linkUserToClient(clientId: string, userEmail: string): Promise<boolean> {
    this.logger.log(`Linking user ${userEmail} to client ${clientId}`);

    try {
      const client = await this.clientRepository.findOne({
        where: { id: clientId },
        relations: ['linkedUsers'],
      });

      if (!client) {
        throw new NotFoundException(`Client with ID ${clientId} not found`);
      }

      // Validate user link limit
      const canLinkUser = await this.validateUserLinkLimit(clientId);
      if (!canLinkUser) {
        throw new BadRequestException('Client has reached maximum user limit for their plan');
      }

      // Check if user already exists
      const user = await this.userSevice.findUserByEmail(userEmail);
      if (!user) {
        throw new NotFoundException(`User with email ${userEmail} not found`);
      }
      // Check if already linked
      if (user.linkedClientId === clientId) {
        throw new ConflictException(`User with email ${userEmail} is already linked to client ${clientId}`);
      }

      // Link user to client (use linkedClientId, not clientId)
      await this.userSevice.linkUserToClient(user.id, clientId);
      this.logger.log(`User ${userEmail} linked to client ${clientId} successfully`);

      // Send email notification
      await this.sendLinkingEmailAsync(user.email, user.firstName);

      return true;
    } catch (error) {
      this.logger.error(`Error linking user to client: ${error.message}`, error.stack);
      throw new BadRequestException(`Failed to link user to client: ${error.message}`);
    }
  }

  async updateClient(clientId: string, updateData: UpdateClientDto): Promise<ClientResponseDto> {
    this.logger.log(`Updating client ${clientId} with data: ${JSON.stringify(updateData)}`);

    try {
      const client = await this.clientRepository.findOne({
        where: { id: clientId },
      });

      if (!client) {
        throw new NotFoundException(`Client with ID ${clientId} not found`);
      }

      // Check email uniqueness if email is being updated
      if (updateData.email && updateData.email !== client.email) {
        const existingClient = await this.clientRepository.findOne({
          where: { email: updateData.email },
        });

        if (existingClient) {
          throw new ConflictException('Client with this email already exists');
        }
      }

      Object.assign(client, updateData);
      const updatedClient = await this.clientRepository.save(client);

      this.logger.log(`Client ${clientId} updated successfully`);
      return this.mapToClientResponseDto(updatedClient);
    } catch (error) {
      this.logger.error(`Error updating client: ${error.message}`, error.stack);
      throw new BadRequestException(`Failed to update client: ${error.message}`);
    }
  }

  async updateClientPlan(clientId: string, planDataDto: PlanDataDto): Promise<PlanResponseDto> {
    this.logger.log(`Updating plan for client ${clientId} to: ${JSON.stringify(planDataDto)}`);

    try {
      const client = await this.clientRepository.findOne({
        where: { id: clientId },
        relations: ['linkedUsers'],
      });

      if (!client) {
        throw new NotFoundException(`Client with ID ${clientId} not found`);
      }

      // Validate that current user count doesn't exceed new plan limits
      const currentUserCount = client.linkedUsers?.length || 0;
      if (currentUserCount > planDataDto.maxUsers) {
        throw new BadRequestException(
          `Cannot downgrade plan. Current user count (${currentUserCount}) exceeds new plan limit (${planDataDto.maxUsers})`,
        );
      }

      client.plan = planDataDto.plan;
      client.maxUsers = planDataDto.maxUsers;

      const updatedClient = await this.clientRepository.save(client);

      this.logger.log(`Plan updated for client ${clientId}`);
      return {
        clientId: updatedClient.id,
        plan: updatedClient.plan,
        maxUsers: updatedClient.maxUsers,
      };
    } catch (error) {
      this.logger.error(`Error updating client plan: ${error.message}`, error.stack);
      throw new BadRequestException(`Failed to update client plan: ${error.message}`);
    }
  }

  async upgradeUserToClient(userId: number, planData: PlanDataDto): Promise<ClientResponseDto> {
    this.logger.log(`Upgrading user ${userId} to client with plan: ${JSON.stringify(planData)}`);

    try {
      // Find the user to upgrade
      const user = await this.userSevice.findUserById(userId);
      if (!user) {
        throw new NotFoundException(`User with ID ${userId} not found`);
      }

      // Check if user is already a client
      if (user.clientId) {
        throw new ConflictException(`User with ID ${userId} is already a client`);
      }

      // Create a new client entity
      const newClient = this.clientRepository.create({
        firstName: user.firstName,
        lastName: user.lastName,
        email: user.email,
        plan: planData.plan,
        maxUsers: planData.maxUsers,
      });

      // Save the new client
      const savedClient = await this.clientRepository.save(newClient);

      await this.userSevice.upgradeUserToClient(userId, savedClient);

      this.logger.log(`User ${userId} upgraded to client with ID ${savedClient.id}`);
      return this.mapToClientResponseDto(savedClient);
    } catch (error) {
      this.logger.error(`Error upgrading user to client: ${error.message}`, error.stack);
      throw new BadRequestException(`Failed to upgrade user to client: ${error.message}`);
    }
  }

  async validateUserLinkLimit(clientId: string): Promise<boolean> {
    this.logger.log(`Validating user link limit for client ${clientId}`);

    try {
      const client = await this.clientRepository.findOne({
        where: { id: clientId },
        relations: ['linkedUsers'],
      });

      if (!client) {
        throw new NotFoundException(`Client with ID ${clientId} not found`);
      }

      const currentUserCount = client.linkedUsers?.length || 0;
      const canLinkMoreUsers = currentUserCount < client.maxUsers;

      this.logger.log(`Client ${clientId} has ${currentUserCount}/${client.maxUsers} users. Can link more: ${canLinkMoreUsers}`);
      return canLinkMoreUsers;
    } catch (error) {
      this.logger.error(`Error validating user link limit: ${error.message}`, error.stack);
      throw new BadRequestException(`Failed to validate user link limit: ${error.message}`);
    }
  }

  // Helper methods
  private mapToClientResponseDto(client: ClientEntity): ClientResponseDto {
    return {
      id: client.id,
      firstName: client.firstName,
      lastName: client.lastName,
      email: client.email,
      plan: client.plan,
      maxUsers: client.maxUsers,
      createdAt: client.createdAt,
      updatedAt: client.updatedAt,
    };
  }
}
