import { HttpStatus, Injectable } from '@nestjs/common';
import { DatabaseService, ErrorResponse, logger } from '../../config';
import { createGeneralError } from '../../utils';
import {
  CreateIpWhitelistDto,
  UpdateIpWhitelistDto,
  ipWhitelistListResponseSchema,
  ipWhitelistResponseSchema,
} from './ip-whitelist.dto';

@Injectable()
export class IpWhitelistService {
  constructor(private readonly db: DatabaseService) {}

  /**
   * Helper to normalize client IP (handles IPv6-mapped IPv4 like ::ffff:192.168.1.5 and localhost)
   */
  normalizeIp(ip: string | undefined | null): string {
    if (!ip) return '127.0.0.1';
    let cleanIp = ip.trim();
    if (cleanIp === '::1' || cleanIp === '::ffff:127.0.0.1') {
      return '127.0.0.1';
    }
    if (cleanIp.startsWith('::ffff:')) {
      cleanIp = cleanIp.substring(7);
    }
    return cleanIp;
  }

  /**
   * Helper to check if a client IP matches an allowed IP or CIDR block
   */
  private matchIpOrCidr(clientIp: string, allowedPattern: string): boolean {
    const pattern = allowedPattern.trim();
    const cleanClient = this.normalizeIp(clientIp);

    // Exact match
    if (pattern === cleanClient) {
      return true;
    }

    // Localhost match
    if (
      (pattern === '127.0.0.1' || pattern === 'localhost') &&
      (cleanClient === '127.0.0.1' || cleanClient === '::1')
    ) {
      return true;
    }

    // CIDR range matching (IPv4)
    if (pattern.includes('/')) {
      try {
        const [range, bitsStr] = pattern.split('/');
        const bits = parseInt(bitsStr, 10);
        if (isNaN(bits) || bits < 0 || bits > 32) return false;

        const ipToInt = (addr: string): number => {
          return (
            addr
              .split('.')
              .reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>> 0
          );
        };

        const clientInt = ipToInt(cleanClient);
        const rangeInt = ipToInt(range);
        const mask = bits === 0 ? 0 : (~0 << (32 - bits)) >>> 0;

        return (clientInt & mask) === (rangeInt & mask);
      } catch (err) {
        logger.error(`Error matching CIDR ${pattern} against ${clientIp}`, err);
        return false;
      }
    }

    return false;
  }

  /**
   * Checks if an IP is allowed under the owner's configured whitelist
   */
  async isIpAllowed(ownerId: string, clientIp: string): Promise<boolean> {
    const activeWhitelists = await this.db.ip_whitelists.findMany({
      where: {
        owner_id: ownerId,
        is_active: true,
      },
    });

    // If no active whitelist records exist for this company, allow access by default (Open Mode)
    if (!activeWhitelists || activeWhitelists.length === 0) {
      return true;
    }

    // Strictly verify client IP against configured rules/subnets
    return activeWhitelists.some((record: any) =>
      this.matchIpOrCidr(clientIp, record.ip_or_cidr),
    );
  }

  async getAll(owner_id: string) {
    try {
      const records = await this.db.ip_whitelists.findMany({
        where: { owner_id },
        orderBy: { created_at: 'desc' },
      });

      const { data, error } = ipWhitelistListResponseSchema.safeParse(records);
      if (error) {
        throw new ErrorResponse(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
      }

      return {
        status: HttpStatus.OK,
        message: 'IP whitelist retrieved successfully',
        data,
      };
    } catch (error) {
      logger.error('Error fetching IP whitelists', error);
      if (error instanceof ErrorResponse) throw error;
      throw new ErrorResponse(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async create(data: CreateIpWhitelistDto, owner_id: string) {
    try {
      const newEntry = await this.db.ip_whitelists.create({
        data: {
          owner_id,
          label: data.label,
          ip_or_cidr: data.ip_or_cidr.trim(),
          is_active: data.is_active ?? true,
        },
      });

      const { data: parsedData, error } = ipWhitelistResponseSchema.safeParse(newEntry);
      if (error) {
        throw new ErrorResponse(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
      }

      return {
        status: HttpStatus.CREATED,
        message: 'IP whitelist entry added successfully',
        data: parsedData,
      };
    } catch (error) {
      logger.error('Error creating IP whitelist entry', error);
      if (error instanceof ErrorResponse) throw error;
      throw new ErrorResponse(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async update(id: string, data: UpdateIpWhitelistDto, owner_id: string) {
    try {
      const existing = await this.db.ip_whitelists.findFirst({
        where: { id, owner_id },
      });

      if (!existing) {
        throw new ErrorResponse('IP whitelist entry not found', HttpStatus.NOT_FOUND);
      }

      const updated = await this.db.ip_whitelists.update({
        where: { id },
        data: {
          ...(data.label !== undefined ? { label: data.label } : {}),
          ...(data.ip_or_cidr !== undefined ? { ip_or_cidr: data.ip_or_cidr.trim() } : {}),
          ...(data.is_active !== undefined ? { is_active: data.is_active } : {}),
        },
      });

      const { data: parsedData, error } = ipWhitelistResponseSchema.safeParse(updated);
      if (error) {
        throw new ErrorResponse(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
      }

      return {
        status: HttpStatus.OK,
        message: 'IP whitelist entry updated successfully',
        data: parsedData,
      };
    } catch (error) {
      logger.error('Error updating IP whitelist entry', error);
      if (error instanceof ErrorResponse) throw error;
      throw new ErrorResponse(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  async delete(id: string, owner_id: string) {
    try {
      const existing = await this.db.ip_whitelists.findFirst({
        where: { id, owner_id },
      });

      if (!existing) {
        throw new ErrorResponse('IP whitelist entry not found', HttpStatus.NOT_FOUND);
      }

      await this.db.ip_whitelists.delete({
        where: { id },
      });

      return {
        status: HttpStatus.OK,
        message: 'IP whitelist entry deleted successfully',
      };
    } catch (error) {
      logger.error('Error deleting IP whitelist entry', error);
      if (error instanceof ErrorResponse) throw error;
      throw new ErrorResponse(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
}
