<?php
/**
 * Mapowanie WC_Order na strukturę faktury KSeF
 *
 * Ekstrahuje dane z zamówienia WooCommerce i konwertuje
 * na format wymagany przez generowanie XML FA(3).
 *
 * @package KSEF\WordPress\Invoices
 */

namespace KSEF\WordPress\Invoices;

/**
 * Order_Mapper — mapowanie WC_Order → struktura faktury
 *
 * Obsługuje:
 * - Eksport sprzedawcy (z ustawień)
 * - Parsing nabywcy (billing data + custom NIP field)
 * - Pozycje zamówienia (produkty, ceny, VAT)
 * - Obsługa rabatów i opłat
 * - Mapowanie metody płatności
 */
class Order_Mapper {

	/**
	 * Zamówienie WooCommerce
	 *
	 * @var \WC_Order
	 */
	private $order;

	/**
	 * Kredencjał KSeF (dla danych sprzedawcy)
	 *
	 * @var array
	 */
	private $credential;

	/**
	 * Rozwiązany preset „Dane kontaktowe" (gksef_extra_data_presets) dla kredencjału — lazy cache.
	 *
	 * @var array|null
	 */
	private $contact_preset = null;

	/**
	 * Konstruktor
	 *
	 * @param \WC_Order $order Zamówienie WooCommerce.
	 * @param array     $credential Kredencjał z wp_gksef_credentials.
	 */
	public function __construct( $order, $credential ) {
		$this->order      = $order;
		$this->credential = $credential;
	}

	/**
	 * Mapuj zamówienie na strukturę faktury
	 *
	 * @return array Struktura: seller, buyer, items, totals, payment_method
	 * @throws \Exception
	 */
	public function map() {
		return [
			'seller'           => $this->get_seller(),
			'buyer'            => $this->get_buyer(),
			'items'            => $this->get_items(),
			'totals'           => $this->get_totals(),
			'payment_method'   => $this->get_payment_method(),
			'invoice_number'   => $this->get_invoice_number(),
			'issue_date'       => $this->get_issue_date(),
			'issue_place'      => (string) ( $this->contact_preset()['issue_place'] ?? '' ) ?: (string) ( $this->credential['seller_city'] ?? '' ),
		];
	}

	/**
	 * Pobierz dane sprzedawcy
	 *
	 * Z ustawień wtyczki
	 *
	 * @return array seller: nip, name, address, email, phone
	 */
	/**
	 * Domyślny preset „Dane kontaktowe" dla bieżącego kredencjału: najpierw przypisany do tego
	 * konta KSeF (domyślny ★, potem pierwszy), potem przypisany do „wszystkich kont".
	 *
	 * @return array<string,mixed>
	 */
	private function contact_preset(): array {
		if ( null !== $this->contact_preset ) {
			return $this->contact_preset;
		}
		$this->contact_preset = [];
		$cred_id = (int) ( $this->credential['id'] ?? 0 );
		$presets = get_option( 'gksef_extra_data_presets', [] );
		if ( is_array( $presets ) ) {
			$for_cred = [];
			$for_all  = [];
			foreach ( $presets as $p ) {
				if ( ! is_array( $p ) ) {
					continue;
				}
				$pcid = (int) ( $p['credential_id'] ?? 0 );
				if ( $cred_id > 0 && $pcid === $cred_id ) {
					$for_cred[] = $p;
				} elseif ( 0 === $pcid ) {
					$for_all[] = $p;
				}
			}
			foreach ( [ $for_cred, $for_all ] as $list ) {
				foreach ( $list as $p ) {
					if ( ! empty( $p['is_default'] ) ) {
						$this->contact_preset = $p;
						return $this->contact_preset;
					}
				}
				if ( ! empty( $list ) ) {
					$this->contact_preset = $list[0];
					return $this->contact_preset;
				}
			}
		}
		return $this->contact_preset;
	}

	private function get_seller() {
		$c      = $this->credential;
		$preset = $this->contact_preset();
		// Kontakt sprzedawcy: preset „Dane kontaktowe" → pola kredencjału → e-mail admina (ostateczność).
		$email = (string) ( $preset['seller_email'] ?? '' );
		if ( '' === $email ) {
			$email = (string) ( $c['seller_email'] ?? '' );
		}
		if ( '' === $email ) {
			$email = (string) get_option( 'admin_email' );
		}
		$phone = (string) ( $preset['seller_phone'] ?? '' );
		if ( '' === $phone ) {
			$phone = (string) ( $c['seller_phone'] ?? '' );
		}
		return [
			'nip'     => $c['nip'],
			'name'    => (string) ( $c['seller_name'] ?: ( $c['name'] ?? get_option( 'gksef_seller_name', 'Twoja Firma' ) ) ),
			'address' => $this->get_seller_address(),
			'email'   => $email,
			'phone'   => $phone,
		];
	}

	/**
	 * Pobierz adres sprzedawcy
	 *
	 * @return array address: street, city, postal_code, country
	 */
	private function get_seller_address() {
		$c = $this->credential;
		return [
			'street'        => (string) ( $c['seller_street'] ?? '' ),
			'street_number' => (string) ( $c['seller_building'] ?? '' ),
			'city'          => (string) ( $c['seller_city'] ?? '' ),
			'postal_code'   => (string) ( $c['seller_postal'] ?? '' ),
			'country'       => (string) ( $c['seller_country'] ?: 'PL' ),
		];
	}

	/**
	 * Pobierz dane nabywcy
	 *
	 * Z danych billing zamówienia + custom NIP field
	 *
	 * @return array buyer: nip/pesel, name, address, email, phone
	 */
	private function get_buyer() {
		$first_name = $this->order->get_billing_first_name();
		$last_name  = $this->order->get_billing_last_name();
		$company    = $this->order->get_billing_company();
		$nip        = $this->order->get_meta( '_billing_nip', true );

		// Jeśli jest firma — użyj jako name, inaczej imię + nazwisko
		$name = $company ? $company : trim( $first_name . ' ' . $last_name );

		return [
			'nip'     => sanitize_text_field( $nip ?? '' ),
			'name'    => $name,
			'address' => [
				'street'        => $this->order->get_billing_address_1(),
				'street_number' => '', // WC nie ma oddzielnego pola
				'city'          => $this->order->get_billing_city(),
				'postal_code'   => $this->order->get_billing_postcode(),
				'country'       => $this->order->get_billing_country() ?? 'PL',
			],
			'email'   => $this->order->get_billing_email(),
			'phone'   => $this->order->get_billing_phone(),
		];
	}

	/**
	 * Pobierz pozycje zamówienia
	 *
	 * @return array Lista: [item_index => [description, qty, unit, net_price, vat_rate, vat_amount, gross_price]]
	 */
	private function get_items() {
		$items = [];
		$item_index = 1;

		foreach ( $this->order->get_items( 'line_item' ) as $item ) {
			/** @var \WC_Order_Item_Product $item */

			$product = $item->get_product();
			if ( ! $product ) {
				continue;
			}

			$quantity   = $item->get_quantity();
			$unit_price = floatval( $item->get_subtotal() ) / $quantity;

			// Pobierz stawkę VAT
			$tax_data  = $item->get_taxes();
			$vat_rate  = $this->extract_vat_rate( $item, $product );
			// Kwoty pozycji spójnie ze stawką: gdy WooCommerce naliczył podatek — użyj go; gdy 0,
			// a stawka > 0 (z konfiguracji) — policz VAT ze stawki, by stawka/VAT/brutto się zgadzały
			// (inaczej np. stawka 5% przy VAT 0 i brutto = netto).
			$net_g  = (int) round( floatval( $item->get_subtotal() ) * 100 );
			$wc_tax = floatval( $item->get_subtotal_tax() );
			$rate_n = is_numeric( $vat_rate ) ? (float) $vat_rate : 0.0;
			$vat_g  = ( abs( $wc_tax ) >= 0.005 ) ? (int) round( $wc_tax * 100 ) : (int) round( $net_g * $rate_n / 100 );

			$items[ $item_index ] = [
				'line_number'   => $item_index,
				'description'   => $product->get_name(),
				'sku'           => $product->get_sku() ?? '',
				'quantity'      => $quantity,
				'unit'          => 'szt', // TODO: mapuj z product meta
				'unit_net_price' => $this->round_price( $unit_price ),
				'vat_rate'      => $vat_rate,
				'net_amount'    => $net_g / 100,
				'vat_amount'    => $vat_g / 100,
				'gross_amount'  => ( $net_g + $vat_g ) / 100,
			];

			$item_index++;
		}

		// Rabaty (fee items — negatywna wartość)
		foreach ( $this->order->get_items( 'fee' ) as $fee ) {
			/** @var \WC_Order_Item_Fee $fee */

			$fee_amount = abs( floatval( $fee->get_amount() ) );
			$fee_tax    = abs( floatval( $fee->get_total_tax() ) );

			if ( $fee_amount <= 0 ) {
				continue;
			}

			// Rabat (negatywna wartość)
			$items[ $item_index ] = [
				'line_number'    => $item_index,
				'description'    => $fee->get_name(),
				'sku'            => '',
				'quantity'       => 1,
				'unit'           => 'szt',
				'unit_net_price' => -$this->round_price( $fee_amount ),
				'vat_rate'       => $fee_amount > 0 ? (int) round( $fee_tax / $fee_amount * 100.0 ) : 0,
				'net_amount'     => -$this->round_price( $fee_amount ),
				'vat_amount'     => -$this->round_price( $fee_tax ),
				'gross_amount'   => -$this->round_price( $fee_amount + $fee_tax ),
			];

			$item_index++;
		}

		// Opłata za dostawę
		if ( $this->order->get_shipping_total() > 0 ) {
			$shipping_total = floatval( $this->order->get_shipping_total() );
			$shipping_tax   = floatval( $this->order->get_shipping_tax() );

			$items[ $item_index ] = [
				'line_number'    => $item_index,
				'description'    => 'Dostawa',
				'sku'            => '',
				'quantity'       => 1,
				'unit'           => 'szt',
				'unit_net_price' => $this->round_price( $shipping_total ),
				'vat_rate'       => $shipping_total > 0 ? (int) round( $shipping_tax / $shipping_total * 100.0 ) : 0,
				'net_amount'     => $this->round_price( $shipping_total ),
				'vat_amount'     => $this->round_price( $shipping_tax ),
				'gross_amount'   => $this->round_price( $shipping_total + $shipping_tax ),
			];
		}

		return $items;
	}

	/**
	 * Ekstrahuj stawkę VAT z pozycji
	 *
	 * @param \WC_Order_Item_Product $item Pozycja.
	 * @param \WC_Product            $product Produkt.
	 * @return int Stawka: 23, 8, 5, 0, etc.
	 */
	private function extract_vat_rate( $item, $product = null ) {
		// Realna stawka VAT zastosowana przez WooCommerce na tej pozycji — uwzględnia KRAJ
		// nabywcy, KLASĘ PODATKOWĄ produktu i skonfigurowane stawki (WooCommerce → Podatki).
		// Liczymy ją z faktycznego podatku/netto pozycji, więc zawsze zgadza się z tym, co
		// WooCommerce naliczył w zamówieniu (zamiast zahardkowanych 23%).
		$net = (float) $item->get_subtotal();
		$tax = (float) $item->get_subtotal_tax();
		if ( abs( $net ) < 0.0001 ) {
			$net = (float) $item->get_total();
			$tax = (float) $item->get_total_tax();
		}
		if ( abs( $net ) > 0.0001 && abs( $tax ) > 0.0001 ) {
			return (int) round( $tax / $net * 100.0 );
		}
		// Brak naliczonego podatku na pozycji → odczytaj stawkę z konfiguracji WC (klasa + kraj),
		// np. gdy obowiązuje stawka 0%. Brak konfiguracji → 0.
		$cfg = $this->rate_from_wc_settings( $item, $product );
		return null !== $cfg ? $cfg : 0;
	}

	/**
	 * Stawka VAT z konfiguracji WooCommerce dla klasy podatkowej produktu i kraju nabywcy
	 * (używane, gdy na pozycji nie naliczono podatku — np. realna stawka 0%). Zwraca int lub null.
	 *
	 * @param \WC_Order_Item $item    Pozycja zamówienia.
	 * @param \WC_Product|null $product Produkt (opcjonalnie).
	 */
	private function rate_from_wc_settings( $item, $product = null ): ?int {
		if ( ! class_exists( 'WC_Tax' ) ) {
			return null;
		}
		if ( ! $product && is_callable( [ $item, 'get_product' ] ) ) {
			$product = $item->get_product();
		}
		$tax_class = $product ? (string) $product->get_tax_class() : '';
		$country   = (string) ( $this->order->get_billing_country() ?: $this->order->get_shipping_country() );
		$rates     = \WC_Tax::find_rates( [ 'country' => $country, 'tax_class' => $tax_class ] );
		foreach ( (array) $rates as $r ) {
			if ( isset( $r['rate'] ) && is_numeric( $r['rate'] ) ) {
				return (int) round( (float) $r['rate'] );
			}
		}
		return null;
	}

	/**
	 * Pobierz sumy faktury (netto, VAT pogrupowany, brutto)
	 *
	 * @return array totals: net, vat_by_rate, gross
	 */
	private function get_totals() {
		$net_total = floatval( $this->order->get_subtotal() );
		$tax_total = floatval( $this->order->get_total_tax() );
		$gross_total = floatval( $this->order->get_total() );

		// Pogrupuj VAT po stawkach
		$vat_by_rate = $this->group_vat_by_rate();

		return [
			'net_amount'  => $this->round_price( $net_total ),
			'vat_by_rate' => $vat_by_rate,
			'vat_total'   => $this->round_price( $tax_total ),
			'gross_amount' => $this->round_price( $gross_total ),
		];
	}

	/**
	 * Pogrupuj VAT po stawkach
	 *
	 * Zwraca mapę: stawka => kwota VAT
	 *
	 * @return array vat_by_rate: [23 => 100.50, 8 => 50.25, ...]
	 */
	private function group_vat_by_rate() {
		$vat_by_rate = [];

		// Zbierz z pozycji (line items)
		foreach ( $this->order->get_items( 'line_item' ) as $item ) {
			$vat_rate = $this->extract_vat_rate( $item, $item->get_product() );
			$vat_amount = floatval( $item->get_total_tax() );

			if ( ! isset( $vat_by_rate[ $vat_rate ] ) ) {
				$vat_by_rate[ $vat_rate ] = 0;
			}

			$vat_by_rate[ $vat_rate ] += $vat_amount;
		}

		// Zaokrąglij
		foreach ( $vat_by_rate as &$amount ) {
			$amount = $this->round_price( $amount );
		}

		return $vat_by_rate;
	}

	/**
	 * Pobierz metodę płatności
	 *
	 * Mapuj WC payment method na formę KSeF
	 *
	 * @return array payment_method: type, bank_account, swift, deadline_days
	 */
	private function get_payment_method() {
		$payment_method = $this->order->get_payment_method();

		// Mapowanie: WC method → KSeF form
		$form_map = [
			'bacs'          => 'BANK_TRANSFER', // Direct bank transfer
			'cheque'        => 'CHECK',
			'cod'           => 'CASH',           // Cash on delivery
			'stripe'        => 'CARD_PAYMENT',
			'paypal'        => 'OTHER_ELECTRONIC_PAYMENT',
			'woocommerce_payments' => 'CARD_PAYMENT',
		];

		$form = $form_map[ $payment_method ] ?? 'OTHER_ELECTRONIC_PAYMENT';

		return [
			'type'         => $form,
			'bank_account' => get_option( 'gksef_seller_bank_account', '' ),
			'swift'        => get_option( 'gksef_seller_swift', '' ),
			'deadline_days' => intval( get_option( 'gksef_payment_deadline_days', 14 ) ),
		];
	}

	/**
	 * Pobierz numer faktury (invoice number)
	 *
	 * Format: {rok}/{nr.porządkowy} np. 1/2025
	 * TODO: integracja z plugin'ami do numeracji faktur
	 *
	 * @return string
	 */
	private function get_invoice_number() {
		$order_id = $this->order->get_id();
		$year     = gmdate( 'Y' );

		// Domyślnie: order ID + rok
		// TODO: zastąp z plugin'ami typu WooCommerce PDF Invoices, Automatic PDF Generator
		return "{$order_id}/{$year}";
	}

	/**
	 * Pobierz datę wystawienia faktury
	 *
	 * Data ukończenia zamówienia
	 *
	 * @return string YYYY-MM-DD
	 */
	private function get_issue_date() {
		$date_completed = $this->order->get_date_completed();

		if ( $date_completed ) {
			return $date_completed->format( 'Y-m-d' );
		}

		// Fallback: dzisiaj
		return gmdate( 'Y-m-d' );
	}

	/**
	 * Zaokrąglij cenę do 2 miejsc dziesiętnych
	 *
	 * @param float $price Cena.
	 * @return float
	 */
	private function round_price( $price ) {
		return round( floatval( $price ), 2 );
	}
}
