<?php
/**
 * WHOIS Lookup Handler
 *
 * @package VMPFence
 * @subpackage Tools
 * @since 2.1.0
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Class VMPFence_Whois_Lookup
 * Handles WHOIS lookups for IP addresses and domains
 */
class VMPFence_Whois_Lookup {

    /**
     * API endpoint for WHOIS lookups
     */
    const API_ENDPOINT = 'http://ipwhois.app/json/';

    /**
     * Cache duration (1 hour)
     */
    const CACHE_DURATION = 3600;

    /**
     * Sanitize and validate input (IP or domain)
     *
     * @param string $input Raw user input
     * @return string|false Sanitized input or false if invalid
     */
    public static function sanitize_input($input) {
        if (empty($input)) {
            return false;
        }

        // Strip all whitespace
        $input = preg_replace('/\s+/', '', $input);

        // URL decode if encoded
        $input = urldecode($input);

        // Convert to lowercase
        $input = strtolower($input);

        // Remove special/invalid characters
        $input = preg_replace('/[<>{}|\\\\^`\[\]"\']/', '', $input);

        // Handle multiple protocols
        while (preg_match('/^[a-z]+:\/\//', $input)) {
            $input = preg_replace('/^[a-z]+:\/\//', '', $input);
        }

        // Parse as URL to extract host
        if (strpos($input, '/') !== false || strpos($input, '@') !== false) {
            $parsed = parse_url('http://' . $input);
            if (isset($parsed['host'])) {
                $input = $parsed['host'];
            } elseif (isset($parsed['path'])) {
                $path_parts = explode('/', $parsed['path']);
                $input = $path_parts[0];
            }
        }

        // Remove user:password@ prefix
        if (strpos($input, '@') !== false) {
            $parts = explode('@', $input);
            $input = end($parts);
        }

        // Handle IPv6 brackets
        if (strpos($input, '[') === 0 && strpos($input, ']') !== false) {
            $parts = explode('[', $input);
            $input = $parts[1];
            $parts = explode(']', $input);
            $input = $parts[0];
        }
        // Remove port for IPv4/domains (preserve IPv6 colons)
        elseif (strpos($input, ':') !== false) {
            // Check if IPv6 (multiple colons)
            if (substr_count($input, ':') >= 2) {
                // IPv6, keep as is
            } else {
                // Port, remove it
                $parts = explode(':', $input);
                $input = $parts[0];
            }
        }

        // Remove CIDR notation
        if (strpos($input, '/') !== false) {
            $parts = explode('/', $input);
            $input = $parts[0];
        }

        // Remove www. prefix
        while (strpos($input, 'www.') === 0) {
            $input = substr($input, 4);
        }

        // Remove leading/trailing dots
        $input = trim($input, '.');

        // Remove multiple consecutive dots
        $input = preg_replace('/\.{2,}/', '.', $input);

        // Remove any remaining invalid characters (keep colons for IPv6)
        $input = preg_replace('/[^a-z0-9.\-:]/', '', $input);

        // Final trim
        $input = trim($input);

        // Validate result
        if (empty($input)) {
            return false;
        }

        // Check if valid IP or domain
        if (strpos($input, ':') !== false) {
            // IPv6
            return (substr_count($input, ':') >= 2) ? $input : false;
        } elseif (self::is_valid_ipv4($input)) {
            return $input;
        } elseif (self::is_valid_domain($input)) {
            return $input;
        }

        return false;
    }

    /**
     * Validate IPv4 address
     *
     * @param string $ip IP address to validate
     * @return bool
     */
    private static function is_valid_ipv4($ip) {
        $parts = explode('.', $ip);
        if (count($parts) !== 4) {
            return false;
        }

        foreach ($parts as $part) {
            // Reject leading zeros (001, 01, etc.) except for '0' itself
            if (strlen($part) > 1 && substr($part, 0, 1) === '0') {
                return false;
            }
            // Validate numeric and range
            if (!is_numeric($part)) {
                return false;
            }
            $num = intval($part);
            if ($num < 0 || $num > 255) {
                return false;
            }
        }

        return true;
    }

    /**
     * Validate domain name
     *
     * @param string $domain Domain to validate
     * @return bool
     */
    private static function is_valid_domain($domain) {
        if (empty($domain) || strlen($domain) > 253) {
            return false;
        }

        // Reject if it looks like an IPv4 address (all numeric labels)
        $parts = explode('.', $domain);
        $all_numeric = true;
        foreach ($parts as $part) {
            if (!is_numeric($part)) {
                $all_numeric = false;
                break;
            }
        }
        if ($all_numeric && count($parts) === 4) {
            return false; // This is likely an IP address
        }

        // Domain must contain at least one letter (not all numbers)
        if (!preg_match('/[a-z]/', $domain)) {
            return false;
        }

        $pattern = '/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/';
        return (bool) preg_match($pattern, $domain);
    }

    /**
     * Resolve domain to IPv4 and IPv6 addresses
     *
     * @param string $domain Domain to resolve
     * @return array Array with 'ipv4' and 'ipv6' arrays
     */
    private static function resolve_domain($domain) {
        $result = array('ipv4' => array(), 'ipv6' => array());

        // Get DNS records
        $records_a = @dns_get_record($domain, DNS_A);
        $records_aaaa = @dns_get_record($domain, DNS_AAAA);

        // Extract IPv4 addresses
        if (is_array($records_a)) {
            foreach ($records_a as $record) {
                if (isset($record['ip']) && !in_array($record['ip'], $result['ipv4'])) {
                    $result['ipv4'][] = $record['ip'];
                }
            }
        }

        // Extract IPv6 addresses
        if (is_array($records_aaaa)) {
            foreach ($records_aaaa as $record) {
                if (isset($record['ipv6']) && !in_array($record['ipv6'], $result['ipv6'])) {
                    $result['ipv6'][] = $record['ipv6'];
                }
            }
        }

        return $result;
    }

    /**
     * Perform WHOIS lookup
     *
     * @param string $target IP address or domain
     * @return array|WP_Error Result array or error
     */
    public static function lookup($target) {
        // Sanitize input
        $sanitized = self::sanitize_input($target);

        if (!$sanitized) {
            return new WP_Error('invalid_input', __('Invalid IP address or domain name.', 'vmpfence-security'));
        }

        $results = array('data' => array());
        $is_domain = self::is_valid_domain($sanitized);

        if ($is_domain) {
            // Domain: resolve to IPs and lookup each
            $resolved = self::resolve_domain($sanitized);

            $total_ips = count($resolved['ipv4']) + count($resolved['ipv6']);

            if ($total_ips === 0) {
                return new WP_Error('dns_failed', __('Could not resolve domain to any IP addresses.', 'vmpfence-security'));
            }

            $results['resolved'] = array(
                'domain' => $sanitized,
                'ipv4_count' => count($resolved['ipv4']),
                'ipv6_count' => count($resolved['ipv6'])
            );

            // Track failed lookups for better error messaging
            $failed_lookups = 0;
            $total_lookups = $total_ips;

            // Lookup IPv4 addresses
            if (!empty($resolved['ipv4'])) {
                foreach ($resolved['ipv4'] as $index => $ip) {
                    $lookup_result = self::lookup_single_ip($ip);
                    if (!is_wp_error($lookup_result)) {
                        $lookup_result['ip_type'] = 'IPv4';
                        $lookup_result['ip_index'] = $index + 1;
                        $results['data'][] = $lookup_result;
                    } else {
                        $failed_lookups++;
                    }
                }
            }

            // Lookup IPv6 addresses
            if (!empty($resolved['ipv6'])) {
                foreach ($resolved['ipv6'] as $index => $ip) {
                    $lookup_result = self::lookup_single_ip($ip);
                    if (!is_wp_error($lookup_result)) {
                        $lookup_result['ip_type'] = 'IPv6';
                        $lookup_result['ip_index'] = $index + 1;
                        $results['data'][] = $lookup_result;
                    } else {
                        $failed_lookups++;
                    }
                }
            }

            // Check if we got any results
            if (empty($results['data'])) {
                if ($failed_lookups === $total_lookups) {
                    return new WP_Error('lookup_failed', __('All IP lookups failed. The API may be temporarily unavailable.', 'vmpfence-security'));
                }
                return new WP_Error('lookup_failed', __('No valid results returned from API.', 'vmpfence-security'));
            }

        } else {
            // Direct IP: single lookup
            $lookup_result = self::lookup_single_ip($sanitized);
            if (is_wp_error($lookup_result)) {
                return $lookup_result;
            }

            // Determine IP type
            if (strpos($sanitized, ':') !== false) {
                $lookup_result['ip_type'] = 'IPv6';
            } else {
                $lookup_result['ip_type'] = 'IPv4';
            }

            $results['data'] = array($lookup_result);
        }

        return $results;
    }

    /**
     * Lookup single IP address
     *
     * @param string $ip IP address
     * @return array|WP_Error WHOIS data or error
     */
    private static function lookup_single_ip($ip) {
        // Check cache first
        $cache_key = 'vmpfence_whois_' . md5($ip);
        $cached = get_transient($cache_key);

        if ($cached !== false) {
            return $cached;
        }

        // Make API request
        $response = wp_remote_get(
            self::API_ENDPOINT . $ip,
            array(
                'timeout' => 10,
                'headers' => array(
                    'User-Agent' => 'VMPFence/2.0 WordPress-Security-Plugin',
                    'Accept' => 'application/json'
                )
            )
        );

        // Check for errors
        if (is_wp_error($response)) {
            return new WP_Error('api_error', $response->get_error_message());
        }

        $status_code = wp_remote_retrieve_response_code($response);
        if ($status_code !== 200) {
            return new WP_Error('api_error', sprintf(__('API returned status code %d', 'vmpfence-security'), $status_code));
        }

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);

        // Validate response structure
        if (!$data || !is_array($data)) {
            return new WP_Error('parse_error', __('Could not parse API response.', 'vmpfence-security'));
        }

        // Check API success status
        if (!isset($data['success']) || $data['success'] !== true) {
            $error_message = isset($data['message']) ? $data['message'] : __('API request failed.', 'vmpfence-security');
            return new WP_Error('api_error', $error_message);
        }

        // Cache the result
        set_transient($cache_key, $data, self::CACHE_DURATION);

        return $data;
    }
}
