<?php
namespace Bookaro\Service;

defined( 'ABSPATH' ) || exit;

use Bookaro\Repository\BookingRepository;

/**
 * Stores sanitized notification attempts for bookings.
 */
class NotificationLogService
{
    private BookingRepository $db;

    public function __construct( ?BookingRepository $db = null )
    {
        $this->db = $db ?: new BookingRepository();
    }

    /**
     * Record a notification attempt for a booking.
     */
    public function record(
        int $booking_id,
        string $channel,
        string $status,
        array $summary = [],
        string $error_message = '',
        string $template_key = ''
    ): void {
        $booking_id = absint( $booking_id );
        if ( $booking_id < 1 ) {
            return;
        }

        $channel = $this->sanitize_key( $channel, false );
        $status  = $this->sanitize_key( $status, false );
        if ( $channel === '' || $status === '' ) {
            return;
        }

        $template_key = $this->sanitize_key( $template_key, true );
        $summary = $this->sanitize_summary( $summary );
        $summary_json = $summary ? wp_json_encode( $summary ) : '';

        $error_message = $this->truncate( $this->redact_error_message( $error_message ), 200 );

        $this->db->insert_notification_log(
            $booking_id,
            $channel,
            $status,
            is_string( $summary_json ) ? $summary_json : '',
            $error_message,
            $template_key
        );
    }

    private function sanitize_summary( array $summary ): array
    {
        $clean = [];
        foreach ( $summary as $key => $value ) {
            $key = $this->sanitize_key( (string) $key, true );
            if ( $key === '' || $this->is_sensitive_key( $key ) ) {
                continue;
            }

            if ( is_array( $value ) ) {
                $value = wp_json_encode( $this->sanitize_summary( $value ) );
            } elseif ( is_bool( $value ) ) {
                $value = $value ? 'true' : 'false';
            } elseif ( is_numeric( $value ) ) {
                $value = (string) $value;
            } elseif ( is_string( $value ) ) {
                $value = $value;
            } else {
                $value = '';
            }

            $value = sanitize_text_field( (string) $value );
            if ( $value === '' ) {
                $clean[ $key ] = '';
                continue;
            }

            if ( in_array( $key, [ 'to', 'email', 'recipient' ], true ) ) {
                $value = $this->mask_email( $value );
            } elseif ( in_array( $key, [ 'phone', 'to_phone' ], true ) ) {
                $value = $this->mask_phone( $value );
            } elseif ( in_array( $key, [ 'endpoint', 'url' ], true ) ) {
                $value = $this->mask_url( $value );
            }

            $clean[ $key ] = $this->truncate( $value, 160 );
        }

        return $clean;
    }

    private function sanitize_key( string $key, bool $allow_dot ): string
    {
        $key = trim( $key );
        if ( $key === '' ) {
            return '';
        }
        $pattern = $allow_dot ? '/[^A-Za-z0-9_\\-.]/' : '/[^A-Za-z0-9_\\-]/';
        $key = preg_replace( $pattern, '', $key );
        return is_string( $key ) ? $key : '';
    }

    private function is_sensitive_key( string $key ): bool
    {
        $blocked = [
            'token',
            'secret',
            'authorization',
            'password',
            'payload',
            'headers',
            'signature',
            'body',
            'message',
            'content',
            'api_key',
            'license',
            'sid',
        ];
        return in_array( $key, $blocked, true );
    }

    private function mask_email( string $email ): string
    {
        $email = trim( $email );
        if ( $email === '' ) {
            return '';
        }
        $parts = explode( '@', $email, 2 );
        if ( count( $parts ) !== 2 ) {
            return $this->truncate( $email, 12 );
        }
        $local = $parts[0];
        $domain = $parts[1];
        $mask = $local !== '' ? substr( $local, 0, 1 ) . '***' : '***';
        return $mask . '@' . $domain;
    }

    private function mask_phone( string $phone ): string
    {
        $phone = trim( $phone );
        if ( $phone === '' ) {
            return '';
        }
        $digits = preg_replace( '/\\D+/', '', $phone );
        if ( ! is_string( $digits ) || $digits === '' ) {
            return '***';
        }
        $last4 = substr( $digits, -4 );
        $prefix = strpos( $phone, '+' ) !== false ? '+' : '';
        return $prefix . '***' . $last4;
    }

    private function mask_url( string $url ): string
    {
        $url = trim( $url );
        if ( $url === '' ) {
            return '';
        }
        $parts = wp_parse_url( $url );
        if ( ! is_array( $parts ) || empty( $parts['host'] ) ) {
            return $this->truncate( $url, 40 );
        }
        $scheme = $parts['scheme'] ?? 'https';
        return $scheme . '://' . $parts['host'];
    }

    private function truncate( string $value, int $max ): string
    {
        $value = trim( $value );
        if ( $value === '' ) {
            return '';
        }
        if ( strlen( $value ) <= $max ) {
            return $value;
        }
        return substr( $value, 0, max( 0, $max - 3 ) ) . '...';
    }

    private function redact_error_message( string $message ): string
    {
        $message = sanitize_text_field( $message );
        if ( $message === '' ) {
            return '';
        }
        // `preg_replace` returns null on regex compile/execute failure. On
        // PHP 8.1+ that null then triggers a deprecation when passed as the
        // subject of the next call. Coalesce back to the previous value so
        // a single failed substitution doesn't poison the pipeline.
        //
        // Bearer-token + secrets-in-query-string regexes use `#` as delimiter
        // because the patterns contain `/` (URL-safe base64) which would
        // otherwise close a `/.../` delimiter prematurely.
        $message = preg_replace( '/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i', '[redacted-email]', $message ) ?? $message;
        $message = preg_replace( '#Bearer\\s+[A-Za-z0-9\\-._~+/]+=*#i', 'Bearer [redacted]', $message ) ?? $message;
        $message = preg_replace( '#(token|key|secret|password|authorization|signature|nonce|api_key|access_token|refresh_token)=([A-Za-z0-9._~\\-]+)#i', '$1=[redacted]', $message ) ?? $message;
        $callback_result = preg_replace_callback(
            '/\\+?\\d[\\d\\s\\-()]{6,}\\d/',
            fn( $m ) => $this->mask_phone( (string) $m[0] ),
            $message
        );
        if ( is_string( $callback_result ) ) {
            $message = $callback_result;
        }
        return $message;
    }
}
