import {
  Injectable,
  Inject,
  Logger,
  ConflictException,
  NotFoundException,
  BadRequestException,
  ForbiddenException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
  InvitationTokenRepository,
  INVITATION_TOKEN_REPOSITORY,
} from '@domain/ports/out/invitation-token.repository';
import { UserRepository, USER_REPOSITORY } from '@domain/ports/out/user.repository';
import {
  OrganizationRepository,
  ORGANIZATION_REPOSITORY,
} from '@domain/ports/out/organization.repository';
import { EmailPort, EMAIL_PORT } from '@domain/ports/out/email.port';
import { InvitationToken } from '@domain/entities/invitation-token.entity';
import { UserRole } from '@domain/entities/user.entity';
import { SubscriptionService } from './subscription.service';
import { v4 as uuidv4 } from 'uuid';
import * as crypto from 'crypto';

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

  constructor(
    @Inject(INVITATION_TOKEN_REPOSITORY)
    private readonly invitationRepository: InvitationTokenRepository,
    @Inject(USER_REPOSITORY)
    private readonly userRepository: UserRepository,
    @Inject(ORGANIZATION_REPOSITORY)
    private readonly organizationRepository: OrganizationRepository,
    @Inject(EMAIL_PORT)
    private readonly emailService: EmailPort,
    private readonly configService: ConfigService,
    private readonly subscriptionService: SubscriptionService
  ) {}

  /**
   * Create an invitation and send email
   */
  async createInvitation(
    email: string,
    firstName: string,
    lastName: string,
    role: UserRole,
    organizationId: string,
    invitedById: string,
    inviterRole?: string
  ): Promise<InvitationToken> {
    this.logger.log(`Creating invitation for ${email} in organization ${organizationId}`);

    // Check if user already exists
    const existingUser = await this.userRepository.findByEmail(email);
    if (existingUser) {
      throw new ConflictException('A user with this email already exists');
    }

    // Check if there's already an active invitation for this email
    const existingInvitation = await this.invitationRepository.findActiveByEmail(email);
    if (existingInvitation) {
      throw new ConflictException(
        'An active invitation for this email already exists. Please wait for it to expire or be used.'
      );
    }

    // Check if licenses are available for this organization
    const canInviteResult = await this.subscriptionService.canInviteUser(
      organizationId,
      inviterRole
    );
    if (!canInviteResult.canInvite) {
      this.logger.warn(
        `License limit reached for organization ${organizationId}: ${canInviteResult.usedLicenses}/${canInviteResult.maxLicenses}`
      );
      throw new ForbiddenException(
        canInviteResult.message ||
          `License limit reached. Please upgrade your subscription to invite more users.`
      );
    }

    // Generate unique token
    const token = this.generateToken();

    // Set expiration date (7 days from now)
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 7);

    // Create invitation token
    const invitation = InvitationToken.create({
      id: uuidv4(),
      token,
      email,
      firstName,
      lastName,
      role,
      organizationId,
      invitedById,
      expiresAt,
    });

    // Save invitation
    const savedInvitation = await this.invitationRepository.save(invitation);

    // Send invitation email (async - don't block on email sending)
    this.logger.log(`[INVITATION] About to send email to ${email}...`);
    this.sendInvitationEmail(savedInvitation).catch(err => {
      this.logger.error(`[INVITATION] ❌ Failed to send invitation email to ${email}`, err);
      this.logger.error(`[INVITATION] Error message: ${err?.message}`);
      this.logger.error(`[INVITATION] Error stack: ${err?.stack?.substring(0, 500)}`);
    });

    this.logger.log(`Invitation created successfully for ${email}`);

    return savedInvitation;
  }

  /**
   * Verify invitation token
   */
  async verifyInvitation(token: string): Promise<InvitationToken> {
    const invitation = await this.invitationRepository.findByToken(token);

    if (!invitation) {
      throw new NotFoundException('Invitation not found');
    }

    if (invitation.isUsed) {
      throw new BadRequestException('This invitation has already been used');
    }

    if (invitation.isExpired()) {
      throw new BadRequestException('This invitation has expired');
    }

    return invitation;
  }

  /**
   * Mark invitation as used
   */
  async markInvitationAsUsed(token: string): Promise<void> {
    const invitation = await this.verifyInvitation(token);

    invitation.markAsUsed();

    await this.invitationRepository.update(invitation);

    this.logger.log(`Invitation ${token} marked as used`);
  }

  /**
   * Get all invitations for an organization
   */
  async getOrganizationInvitations(organizationId: string): Promise<InvitationToken[]> {
    return this.invitationRepository.findByOrganization(organizationId);
  }

  /**
   * Generate a secure random token
   */
  private generateToken(): string {
    return crypto.randomBytes(32).toString('hex');
  }

  /**
   * Send invitation email
   */
  private async sendInvitationEmail(invitation: InvitationToken): Promise<void> {
    this.logger.log(`[INVITATION] 🚀 sendInvitationEmail called for ${invitation.email}`);

    const frontendUrl = this.configService.get<string>('FRONTEND_URL', 'http://localhost:3000');
    const invitationLink = `${frontendUrl}/register?token=${invitation.token}`;

    this.logger.log(`[INVITATION] Frontend URL: ${frontendUrl}`);
    this.logger.log(`[INVITATION] Invitation link: ${invitationLink}`);

    // Get organization details
    this.logger.log(`[INVITATION] Fetching organization ${invitation.organizationId}...`);
    const organization = await this.organizationRepository.findById(invitation.organizationId);
    if (!organization) {
      this.logger.error(`[INVITATION] ❌ Organization not found: ${invitation.organizationId}`);
      throw new NotFoundException('Organization not found');
    }
    this.logger.log(`[INVITATION] ✅ Organization found: ${organization.name}`);

    // Get inviter details
    this.logger.log(`[INVITATION] Fetching inviter ${invitation.invitedById}...`);
    const inviter = await this.userRepository.findById(invitation.invitedById);
    if (!inviter) {
      this.logger.error(`[INVITATION] ❌ Inviter not found: ${invitation.invitedById}`);
      throw new NotFoundException('Inviter user not found');
    }

    const inviterName = `${inviter.firstName} ${inviter.lastName}`;
    this.logger.log(`[INVITATION] ✅ Inviter found: ${inviterName}`);

    try {
      this.logger.log(`[INVITATION] 📧 Calling emailService.sendInvitationWithToken...`);
      await this.emailService.sendInvitationWithToken(
        invitation.email,
        invitation.firstName,
        invitation.lastName,
        organization.name,
        inviterName,
        invitationLink,
        invitation.expiresAt
      );

      this.logger.log(`[INVITATION] ✅ Email sent successfully to ${invitation.email}`);
    } catch (error) {
      this.logger.error(
        `[INVITATION] ❌ Failed to send invitation email to ${invitation.email}`,
        error
      );
      this.logger.error(`[INVITATION] Error details: ${JSON.stringify(error, null, 2)}`);
      throw error;
    }
  }

  /**
   * Cancel (delete) a pending invitation
   */
  async cancelInvitation(invitationId: string, organizationId: string): Promise<void> {
    const invitations = await this.invitationRepository.findByOrganization(organizationId);
    const invitation = invitations.find(inv => inv.id === invitationId);

    if (!invitation) {
      throw new NotFoundException('Invitation not found');
    }

    if (invitation.isUsed) {
      throw new BadRequestException('Cannot delete an invitation that has already been used');
    }

    await this.invitationRepository.deleteById(invitationId);
    this.logger.log(`Invitation ${invitationId} cancelled`);
  }

  /**
   * Cleanup expired invitations (can be called by a cron job)
   */
  async cleanupExpiredInvitations(): Promise<number> {
    const count = await this.invitationRepository.deleteExpired();
    this.logger.log(`Cleaned up ${count} expired invitations`);
    return count;
  }
}
