<?php
/**
 * REST endpoint controller for payments.
 *
 * @since 1.0.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class ZINV_REST_Payments {

	private string $namespace;
	private string $rest_base;
	private ZINV_Payment $payment;

	public function __construct( string $namespace ) {
		$this->namespace = $namespace;
		$this->rest_base = 'payments';
		$this->payment   = new ZINV_Payment();
	}

	public function register(): void {
		register_rest_route( $this->namespace, '/' . $this->rest_base, [
			[
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_items' ],
				'permission_callback' => [ $this, 'check_read_permission' ],
				'args'                => $this->get_collection_params(),
			],
		] );

		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)', [
			[
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_item' ],
				'permission_callback' => [ $this, 'check_read_permission' ],
				'args'                => $this->get_id_param(),
			],
		] );

		register_rest_route( $this->namespace, '/' . $this->rest_base . '/process', [
			[
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'process_payment' ],
				'permission_callback' => [ $this, 'check_write_permission' ],
				'args'                => $this->get_process_params(),
			],
		] );
	}

	public function check_read_permission(): bool {
		return current_user_can( 'zinv_view_payments' );
	}

	public function check_write_permission(): bool {
		return current_user_can( 'zinv_record_payments' );
	}

	public function get_items( WP_REST_Request $request ): WP_REST_Response {
		$args = [
			'page'       => $request->get_param( 'page' ) ?? 1,
			'per_page'   => min( $request->get_param( 'per_page' ) ?? 20, 100 ),
			'search'     => sanitize_text_field( $request->get_param( 'search' ) ?? '' ),
			'status'     => sanitize_text_field( $request->get_param( 'status' ) ?? '' ),
			'client_id'  => $request->get_param( 'client_id' ) ? absint( $request->get_param( 'client_id' ) ) : 0,
			'invoice_id' => $request->get_param( 'invoice_id' ) ? absint( $request->get_param( 'invoice_id' ) ) : 0,
			'method'     => sanitize_text_field( $request->get_param( 'method' ) ?? '' ),
			'date_from'  => sanitize_text_field( $request->get_param( 'date_from' ) ?? '' ),
			'date_to'    => sanitize_text_field( $request->get_param( 'date_to' ) ?? '' ),
			'orderby'    => sanitize_text_field( $request->get_param( 'orderby' ) ?? 'created_at' ),
			'order'      => sanitize_text_field( $request->get_param( 'order' ) ?? 'DESC' ),
		];

		$result = $this->payment->list( $args );

		$data = [
			'items'       => $result['items'],
			'total'       => (int) $result['total'],
			'total_pages' => (int) $result['total_pages'],
			'page'        => (int) $result['page'],
			'per_page'    => (int) $result['per_page'],
		];

		$response = new WP_REST_Response( $data, 200 );
		$response->header( 'X-WP-Total', (int) $result['total'] );
		$response->header( 'X-WP-TotalPages', (int) $result['total_pages'] );

		return $response;
	}

	public function get_item( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$id      = absint( $request->get_param( 'id' ) );
		$payment = $this->payment->get( $id );

		if ( ! $payment ) {
			return new WP_Error(
				'zinv_payment_not_found',
				__( 'Payment not found.', 'zoventic-invoices' ),
				[ 'status' => 404 ]
			);
		}

		return new WP_REST_Response( $payment, 200 );
	}

	public function process_payment( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$invoice_id = absint( $request->get_param( 'invoice_id' ) );

		if ( ! $invoice_id ) {
			return new WP_Error(
				'zinv_payment_invoice_required',
				__( 'Invoice is required.', 'zoventic-invoices' ),
				[ 'status' => 400 ]
			);
		}

		$amount_raw = $request->get_param( 'amount' );
		$amount     = is_numeric( $amount_raw ) ? (string) $amount_raw : '0';

		if ( bccomp( $amount, '0', 8 ) <= 0 ) {
			return new WP_Error(
				'zinv_payment_amount_invalid',
				__( 'Payment amount must be greater than zero.', 'zoventic-invoices' ),
				[ 'status' => 400 ]
			);
		}

		$allowed_write_statuses = [ 'successful', 'pending' ];
		$status_raw = sanitize_text_field( $request->get_param( 'status' ) ?? 'successful' );
		if ( ! in_array( $status_raw, $allowed_write_statuses, true ) ) {
			$status_raw = 'successful';
		}

		$method_raw   = sanitize_text_field( $request->get_param( 'method' ) ?? 'manual' );
		$allowed_methods = [ 'stripe', 'bank_transfer', 'paypal', 'manual', 'other' ];
		if ( ! in_array( $method_raw, $allowed_methods, true ) ) {
			return new WP_Error( 'zinv_payment_invalid_method', __( 'Invalid payment method.', 'zoventic-invoices' ), [ 'status' => 400 ] );
		}

		$currency_raw = sanitize_text_field( $request->get_param( 'currency' ) ?? '' );
		if ( '' !== $currency_raw ) {
			$currency_raw = strtoupper( $currency_raw );
			if ( 1 !== preg_match( '/^[A-Z]{3}$/', $currency_raw ) ) {
				return new WP_Error( 'zinv_payment_invalid_currency', __( 'Currency must be a 3-letter ISO code.', 'zoventic-invoices' ), [ 'status' => 400 ] );
			}
		}

		$txn_date_raw = sanitize_text_field( $request->get_param( 'transaction_date' ) ?? '' );
		if ( '' !== $txn_date_raw && ! \DateTime::createFromFormat( 'Y-m-d', $txn_date_raw ) && ! \DateTime::createFromFormat( 'Y-m-d H:i:s', $txn_date_raw ) ) {
			return new WP_Error( 'zinv_payment_invalid_date', __( 'Transaction date must be Y-m-d or Y-m-d H:i:s.', 'zoventic-invoices' ), [ 'status' => 400 ] );
		}

		$data = [
			'invoice_id'       => $invoice_id,
			'amount'           => $amount,
			'method'           => $method_raw,
			'status'           => $status_raw,
			'transaction_date' => ! empty( $txn_date_raw ) ? $txn_date_raw : current_time( 'mysql' ),
			'notes'            => sanitize_textarea_field( $request->get_param( 'notes' ) ?? '' ),
		];

		if ( '' !== $currency_raw ) {
			$data['currency'] = $currency_raw;
		}

		$payment_id = $this->payment->create( $data );

		if ( ! $payment_id ) {
			return new WP_Error(
				'zinv_payment_process_failed',
				__( 'Failed to record payment. The invoice may not exist.', 'zoventic-invoices' ),
				[ 'status' => 400 ]
			);
		}

		$payment = $this->payment->get( $payment_id );
		$response = new WP_REST_Response( $payment, 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $payment_id ) ) );

		do_action( 'zinv_payment_recorded', $payment_id, $data );

		return $response;
	}

	private function get_id_param(): array {
		return [
			'id' => [
				'type'              => 'integer',
				'required'          => true,
				'validate_callback' => fn( $v ) => is_numeric( $v ) && absint( $v ) > 0,
				'sanitize_callback' => 'absint',
			],
		];
	}

	private function get_collection_params(): array {
		return [
			'page' => [
				'type'              => 'integer',
				'default'           => 1,
				'sanitize_callback' => 'absint',
				'validate_callback' => fn( $v ) => is_numeric( $v ) && absint( $v ) >= 1,
			],
			'per_page' => [
				'type'              => 'integer',
				'default'           => 20,
				'sanitize_callback' => 'absint',
				'validate_callback' => fn( $v ) => is_numeric( $v ) && absint( $v ) >= 1 && absint( $v ) <= 100,
			],
			'search' => [
				'type'              => 'string',
				'default'           => '',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'status' => [
				'type'              => 'string',
				'default'           => '',
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => fn( $v ) => empty( $v ) || in_array( $v, [ 'successful', 'pending', 'failed', 'refunded', 'partially_refunded' ], true ),
			],
			'client_id' => [
				'type'              => 'integer',
				'default'           => 0,
				'sanitize_callback' => 'absint',
			],
			'invoice_id' => [
				'type'              => 'integer',
				'default'           => 0,
				'sanitize_callback' => 'absint',
			],
			'method' => [
				'type'              => 'string',
				'default'           => '',
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => fn( $v ) => empty( $v ) || in_array( $v, [ 'stripe', 'bank_transfer', 'paypal', 'manual', 'other' ], true ),
			],
			'date_from' => [
				'type'              => 'string',
				'default'           => '',
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => fn( $v ) => empty( $v ) || (bool) \DateTime::createFromFormat( 'Y-m-d', $v ),
			],
			'date_to' => [
				'type'              => 'string',
				'default'           => '',
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => fn( $v ) => empty( $v ) || (bool) \DateTime::createFromFormat( 'Y-m-d', $v ),
			],
			'orderby' => [
				'type'              => 'string',
				'default'           => 'created_at',
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => fn( $v ) => in_array( $v, [ 'id', 'invoice_id', 'client_id', 'amount', 'status', 'method', 'transaction_date', 'created_at' ], true ),
			],
			'order' => [
				'type'              => 'string',
				'default'           => 'DESC',
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => fn( $v ) => in_array( strtoupper( $v ), [ 'ASC', 'DESC' ], true ),
			],
		];
	}

	private function get_process_params(): array {
		return [
			'invoice_id' => [
				'type'              => 'integer',
				'required'          => true,
				'sanitize_callback' => 'absint',
				'validate_callback' => fn( $v ) => is_numeric( $v ) && absint( $v ) > 0,
			],
			'amount' => [
				'type'              => 'number',
				'required'          => true,
				'sanitize_callback' => static fn( $v ) => is_numeric( $v ) ? (string) $v : '0',
				'validate_callback' => fn( $v ) => is_numeric( $v ) && bccomp( (string) $v, '0', 8 ) > 0,
			],
			'status' => [
				'type'              => 'string',
				'default'           => 'successful',
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => fn( $v ) => in_array( $v, [ 'successful', 'pending' ], true ),
			],
		];
	}
}
