<?php

namespace Devikit\Sellsy;

use Devikit\Sellsy\Admin\Settings;
use Devikit\Sellsy\Api\Client;

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

/**
 * Build Sellsy invoice payloads from WooCommerce orders.
 */
class DocumentBuilder {

	/**
	 * @var Client
	 */
	private $api_client;

	/**
	 * @var Settings
	 */
	private $settings;

	/**
	 * @var TaxMapper
	 */
	private $tax_mapper;

	/**
	 * @param Client    $api_client
	 * @param Settings  $settings
	 * @param TaxMapper $tax_mapper
	 */
	public function __construct( Client $api_client, Settings $settings, TaxMapper $tax_mapper ) {
		$this->api_client = $api_client;
		$this->settings   = $settings;
		$this->tax_mapper = $tax_mapper;
	}

	/**
	 * @param \WC_Order $order
	 * @return true|\WP_Error
	 */
	public function validate_billing_address( $order ) {
		$required = [
			'email'   => $order->get_billing_email(),
			'address' => $order->get_billing_address_1(),
			'city'    => $order->get_billing_city(),
			'postcode'=> $order->get_billing_postcode(),
			'country' => $order->get_billing_country(),
		];

		foreach ( $required as $key => $value ) {
			if ( trim( (string) $value ) === '' ) {
				return new \WP_Error(
					'sellsy_missing_address',
					sprintf(
						/* translators: %s: field name */
						__( 'Missing billing %s. Complete the order address before creating a Sellsy invoice.', 'invoicing-integration-for-sellsy-and-woocommerce' ),
						$key
					)
				);
			}
		}

		return true;
	}

	/**
	 * @param \WC_Order                 $order
	 * @param array{type:string,id:int} $related
	 * @return array|\WP_Error
	 */
	public function build_invoice_payload( $order, $related ) {
		if ( ! is_array( $related ) || empty( $related['id'] ) || empty( $related['type'] ) ) {
			return new \WP_Error( 'sellsy_related_missing', __( 'Sellsy customer reference is missing.', 'invoicing-integration-for-sellsy-and-woocommerce' ) );
		}

		$rows = $this->build_rows( $order );
		if ( is_wp_error( $rows ) ) {
			return $rows;
		}

		$currency = $order->get_currency() ?: 'EUR';
		$date     = gmdate( 'Y-m-d', $order->get_date_created() ? $order->get_date_created()->getTimestamp() : time() );
		$days     = absint( $this->settings->get_option( 'payment_deadline_days', '7' ) );
		if ( $days > 60 ) {
			$days = 60;
		}

		$payload = [
			'related'  => [
				[
					'id'   => (int) $related['id'],
					'type' => $related['type'] === 'individual' ? 'individual' : 'company',
				],
			],
			'date'     => $date,
			'subject'  => sprintf(
				/* translators: %s: order number */
				__( 'Order %s', 'invoicing-integration-for-sellsy-and-woocommerce' ),
				$order->get_order_number()
			),
			'currency' => $currency,
			'rows'     => $rows,
			'note'     => sprintf(
				/* translators: %s: order number */
				__( 'WooCommerce order %s', 'invoicing-integration-for-sellsy-and-woocommerce' ),
				$order->get_order_number()
			),
		];

		// Sellsy ignores deprecated due_date — use payment_terms.xdays instead.
		$xdays_id = $this->api_client->get_xdays_payment_term_id();
		if ( $xdays_id ) {
			$payload['payment_terms'] = [
				'id'     => $xdays_id,
				'code'   => 'xdays',
				'x_days' => $days,
			];
		}

		$rate_category_id = absint( $this->settings->get_option( 'rate_category_id' ) );
		if ( ! $rate_category_id ) {
			$rate_category_id = $this->get_default_rate_category_id();
		}
		if ( $rate_category_id ) {
			$payload['rate_category_id'] = $rate_category_id;
		}

		$lang_id = absint( apply_filters( 'devikit_sellsy_invoice_lang_id', $this->settings->get_option( 'invoice_lang_id' ), $order ) );
		if ( $lang_id ) {
			$payload['lang_id'] = $lang_id;
		}

		$payload = $this->maybe_adjust_rounding( $payload, $order );

		/**
		 * Filter Sellsy invoice payload before API create.
		 *
		 * @param array     $payload
		 * @param \WC_Order $order
		 * @param array     $related
		 */
		return apply_filters( 'devikit_sellsy_invoice_data', $payload, $order, $related );
	}

	/**
	 * @param \WC_Order $order
	 * @return array|\WP_Error
	 */
	private function build_rows( $order ) {
		$rows    = [];
		$unit_id = $this->get_default_unit_id();

		foreach ( $order->get_items( 'line_item' ) as $item ) {
			/** @var \WC_Order_Item_Product $item */
			$qty = (float) $item->get_quantity();
			if ( $qty <= 0 ) {
				continue;
			}

			$line_total = (float) $item->get_total();
			$unit_net   = $line_total / $qty;
			$rate       = $this->tax_mapper->get_item_tax_rate( $item );
			$tax_id     = $this->tax_mapper->resolve_tax_id( $rate, $order );
			if ( is_wp_error( $tax_id ) ) {
				return $tax_id;
			}

			$row = [
				'type'        => 'single',
				'quantity'    => $this->money( $qty, 4 ),
				'unit_amount' => $this->money( $unit_net ),
				'tax_id'      => (int) $tax_id,
				'description' => wp_strip_all_tags( $item->get_name() ),
				'reference'   => $this->get_item_sku( $item ),
			];

			if ( $unit_id ) {
				$row['unit_id'] = $unit_id;
			}

			$rows[] = $row;
		}

		foreach ( $order->get_items( 'shipping' ) as $shipping ) {
			$total = (float) $shipping->get_total();
			if ( abs( $total ) < 0.0001 ) {
				continue;
			}
			$rate   = $this->tax_mapper->get_item_tax_rate( $shipping );
			$tax_id = $this->tax_mapper->resolve_tax_id( $rate, $order );
			if ( is_wp_error( $tax_id ) ) {
				return $tax_id;
			}

			$rows[] = [
				'type'        => 'single',
				'quantity'    => '1',
				'unit_amount' => $this->money( $total ),
				'tax_id'      => (int) $tax_id,
				'description' => wp_strip_all_tags( $shipping->get_name() ?: __( 'Shipping', 'invoicing-integration-for-sellsy-and-woocommerce' ) ),
				'reference'   => 'SHIPPING',
			];
		}

		foreach ( $order->get_items( 'fee' ) as $fee ) {
			$total = (float) $fee->get_total();
			if ( abs( $total ) < 0.0001 ) {
				continue;
			}
			$rate   = $this->tax_mapper->get_item_tax_rate( $fee );
			$tax_id = $this->tax_mapper->resolve_tax_id( $rate, $order );
			if ( is_wp_error( $tax_id ) ) {
				return $tax_id;
			}

			$rows[] = [
				'type'        => 'single',
				'quantity'    => '1',
				'unit_amount' => $this->money( $total ),
				'tax_id'      => (int) $tax_id,
				'description' => wp_strip_all_tags( $fee->get_name() ?: __( 'Fee', 'invoicing-integration-for-sellsy-and-woocommerce' ) ),
				'reference'   => 'FEE',
			];
		}

		if ( empty( $rows ) ) {
			return new \WP_Error(
				'sellsy_empty_rows',
				__( 'The order has no billable lines for Sellsy.', 'invoicing-integration-for-sellsy-and-woocommerce' )
			);
		}

		return $rows;
	}

	/**
	 * Pennylane-style pre-flight rounding: adjust last line unit price if needed.
	 *
	 * @param array     $payload
	 * @param \WC_Order $order
	 * @return array
	 */
	private function maybe_adjust_rounding( array $payload, $order ) {
		if ( empty( $payload['rows'] ) || ! is_array( $payload['rows'] ) ) {
			return $payload;
		}

		$computed = 0.0;
		foreach ( $payload['rows'] as $row ) {
			$qty  = isset( $row['quantity'] ) ? (float) $row['quantity'] : 0.0;
			$unit = isset( $row['unit_amount'] ) ? (float) $row['unit_amount'] : 0.0;
			$rate = $this->lookup_tax_rate_percent( isset( $row['tax_id'] ) ? (int) $row['tax_id'] : 0 );
			$computed += $qty * $unit * ( 1 + ( $rate / 100 ) );
		}

		$order_total = (float) $order->get_total();
		$diff        = $order_total - $computed;
		if ( abs( $diff ) <= 0.01 ) {
			return $payload;
		}

		$last_index = count( $payload['rows'] ) - 1;
		$last       = $payload['rows'][ $last_index ];
		$qty        = isset( $last['quantity'] ) ? (float) $last['quantity'] : 0.0;
		$rate       = $this->lookup_tax_rate_percent( isset( $last['tax_id'] ) ? (int) $last['tax_id'] : 0 );
		$factor      = $qty * ( 1 + ( $rate / 100 ) );

		if ( $factor <= 0 ) {
			return $payload;
		}

		$unit = (float) $last['unit_amount'] + ( $diff / $factor );
		$payload['rows'][ $last_index ]['unit_amount'] = $this->money( $unit );

		return $payload;
	}

	/**
	 * @param int $tax_id
	 * @return float
	 */
	private function lookup_tax_rate_percent( $tax_id ) {
		$taxes = $this->api_client->list_taxes();
		if ( is_wp_error( $taxes ) || ! is_array( $taxes ) ) {
			return 0.0;
		}
		foreach ( $taxes as $tax ) {
			if ( (int) $tax['id'] === (int) $tax_id ) {
				return (float) $tax['rate'];
			}
		}
		return 0.0;
	}

	/**
	 * @return int
	 */
	private function get_default_rate_category_id() {
		$cats = $this->api_client->list_rate_categories();
		if ( is_wp_error( $cats ) || ! is_array( $cats ) ) {
			return 0;
		}
		foreach ( $cats as $cat ) {
			if ( ! empty( $cat['is_default'] ) ) {
				return (int) $cat['id'];
			}
		}
		return ! empty( $cats[0]['id'] ) ? (int) $cats[0]['id'] : 0;
	}

	/**
	 * @return int
	 */
	private function get_default_unit_id() {
		$units = $this->api_client->list_units();
		if ( is_wp_error( $units ) || ! is_array( $units ) ) {
			return 0;
		}
		foreach ( $units as $unit ) {
			$code = isset( $unit['parameters']['code'] ) ? (string) $unit['parameters']['code'] : '';
			if ( $code === 'C62' ) {
				return (int) $unit['id'];
			}
		}
		return ! empty( $units[0]['id'] ) ? (int) $units[0]['id'] : 0;
	}

	/**
	 * @param \WC_Order_Item_Product $item
	 * @return string
	 */
	private function get_item_sku( $item ) {
		$product = $item->get_product();
		if ( $product && $product->get_sku() ) {
			return (string) $product->get_sku();
		}
		return 'ITEM-' . (int) $item->get_id();
	}

	/**
	 * Format amount/quantity as Sellsy string.
	 *
	 * @param float $value
	 * @param int   $decimals
	 * @return string
	 */
	private function money( $value, $decimals = 2 ) {
		return number_format( (float) $value, $decimals, '.', '' );
	}
}
