package org.mustangproject.ZUGFeRD;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mustangproject.BankDetails;
import org.mustangproject.Invoice;
import org.mustangproject.Item;
import org.mustangproject.Product;
import org.mustangproject.TradeParty;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Date;

import static org.junit.jupiter.api.Assertions.*;

class ZUGFeRD1PullProviderTest {

	private ZUGFeRD1PullProvider provider;

	@BeforeEach
	void setUp() {
		provider = new ZUGFeRD1PullProvider();
	}

	@Test
	void should_format_vat_and_round_to_two_decimals_to_create_zugferd_vat_value() {
		assertEquals("19.12", provider.vatFormat(new BigDecimal("19.123")));
		assertEquals("0.00", provider.vatFormat(BigDecimal.ZERO));
	}

	@Test
	void should_format_currency_and_round_to_two_decimals_to_create_xml_amount() {
		assertEquals("12.35", provider.currencyFormat(new BigDecimal("12.345")));
		assertEquals("-1.10", provider.currencyFormat(new BigDecimal("-1.1")));
	}

	@Test
	void should_format_price_and_round_to_four_decimals_to_create_trade_price_value() {
		assertEquals("1.2346", provider.priceFormat(new BigDecimal("1.23456")));
		assertEquals("10.0000", provider.priceFormat(new BigDecimal("10")));
	}

	@Test
	void should_format_quantity_and_round_to_four_decimals_to_create_trade_quantity_value() {
		assertEquals("2.5000", provider.quantityFormat(new BigDecimal("2.5")));
		assertEquals("0.0000", provider.quantityFormat(BigDecimal.ZERO));
	}

	@Test
	void should_return_comfort_profile_and_expected_urn_to_identify_default_profile() {
		Profile profile = provider.getProfile();
		assertNotNull(profile);
		assertEquals("COMFORT", profile.getName());
		assertEquals("urn:ferd:CrossIndustryDocument:invoice:1p0:comfort", profile.getID());
	}

	@Test
	void should_generate_xml_and_return_non_empty_bytes_to_expose_created_document() {
		provider.generateXML(createMinimalInvoice());
		byte[] xmlBytes = provider.getXML();

		assertNotNull(xmlBytes);
		assertTrue(xmlBytes.length > 0);

		String xml = new String(xmlBytes, StandardCharsets.UTF_8);
		assertTrue(xml.contains("<rsm:CrossIndustryDocument"));
		assertTrue(xml.contains("<ram:TypeCode>380</ram:TypeCode>"));
	}

	@Test
	void should_generate_xml_and_include_basic_profile_and_invoice_content_to_build_valid_document() {
		provider.setProfile(Profiles.getByName("BASIC", 1));
		Invoice invoice = createMinimalInvoice();

		provider.generateXML(invoice);
		String xml = new String(provider.getXML(), StandardCharsets.UTF_8);
		assertTrue(xml.contains("urn:ferd:CrossIndustryDocument:invoice:1p0"));
		assertTrue(xml.contains("<ram:GuidelineSpecifiedDocumentContextParameter>"));
		assertTrue(xml.contains("<ram:ID>urn:ferd:CrossIndustryDocument:invoice:1p0:basic</ram:ID>"));
		assertTrue(xml.contains("<ram:ID>INV-1</ram:ID>"));
		assertTrue(xml.contains("<ram:IncludedSupplyChainTradeLineItem>"));
		assertTrue(xml.contains("<!-- generated by: mustangproject.org -->"));
		assertFalse(xml.contains("<ram:Information>"));
		assertFalse(xml.contains("<ram:BICID>"));
	}

	@Test
	void should_validate_invoice_and_throw_exception_when_delivery_date_is_missing_to_prevent_invalid_xml_generation() {
		Invoice invoice = createMinimalInvoice().setDeliveryDate(null);

		IllegalStateException ex = assertThrows(IllegalStateException.class, () -> provider.generateXML(invoice));
		assertEquals("No delivery date provided", ex.getMessage());
	}

	@Test
	void should_validate_payment_terms_and_throw_exception_for_invalid_discount_terms_combination_to_enforce_business_rules() {
		Invoice invoice = createMinimalInvoice();

		IZUGFeRDPaymentDiscountTerms discountTerms = new IZUGFeRDPaymentDiscountTerms() {
			@Override
			public BigDecimal getCalculationPercentage() {
				return new BigDecimal("2.00");
			}

			@Override
			public Date getBaseDate() {
				return new Date(1700000000000L);
			}

			@Override
			public int getBasePeriodMeasure() {
				return 10;
			}

			@Override
			public String getBasePeriodUnitCode() {
				return "DAY";
			}
		};

		IZUGFeRDPaymentTerms paymentTerms = new IZUGFeRDPaymentTerms() {
			@Override
			public String getDescription() {
				return "2% Skonto";
			}

			@Override
			public Date getDueDate() {
				return new Date(1700000000000L);
			}

			@Override
			public IZUGFeRDPaymentDiscountTerms getDiscountTerms() {
				return discountTerms;
			}
		};

		invoice.setPaymentTerms(paymentTerms);

		IllegalStateException ex = assertThrows(IllegalStateException.class, () -> provider.generateXML(invoice));
		assertTrue(ex.getMessage().contains("paymentTerms.dueDate"));
	}

	@Test
	void should_set_profile_and_return_same_instance_to_use_selected_profile() {
		Profile basicV1 = Profiles.getByName("BASIC", 1);
		provider.setProfile(basicV1);

		assertSame(basicV1, provider.getProfile());
		assertEquals("BASIC", provider.getProfile().getName());
	}

	private Invoice createMinimalInvoice() {
		Date issueDate = new Date(1700000000000L);
		Date deliveryDate = new Date(1700000000000L);
		Date dueDate = new Date(1700600000000L);

		TradeParty seller = new TradeParty("Seller GmbH", "Musterstr. 1", "10115", "Berlin", "DE").addBankDetails(new BankDetails("DE88200800000970375700", "COBADEFFXXX").setPaymentMeansInformation("whatever it takes"));
		TradeParty buyer = new TradeParty("Buyer AG", "Beispielweg 2", "80331", "Muenchen", "DE");

		Product product = new Product("Beratung", "IT-Beratung", "C62", new BigDecimal("19.00"));
		Item item = new Item(product, new BigDecimal("10.00"), new BigDecimal("2"))
			.setTax(new BigDecimal("19.00"));

		return new Invoice()
			.setNumber("INV-1")
			.setIssueDate(issueDate)
			.setDeliveryDate(deliveryDate)
			.setDueDate(dueDate)
			.setCurrency("EUR")
			.setSender(seller)
			.setRecipient(buyer)
			.addItem(item);
	}
}
