package ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.impl;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.xml.datatype.DatatypeFactory;

import jakarta.annotation.Resource;
import jakarta.jws.WebParam;
import jakarta.jws.WebService;
import jakarta.xml.bind.JAXB;
import ru.bitel.bgbilling.kernel.container.service.server.AbstractService;
import ru.bitel.bgbilling.modules.tv.common.bean.TvAccount;
import ru.bitel.bgbilling.modules.tv.common.service.TvAccountService;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.TveUtils;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.BillingException;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.BillingException_Exception;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.OrderManagementServiceWS;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.OrderType;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.Service;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.Service.AdditionalParameters;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.ServiceOffering;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.ServiceOrder;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.ServiceSpecification;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.TimeFrame;
import ru.bitel.bgbilling.modules.tv.server.TvUtils;
import ru.bitel.common.TimeUtils;
import ru.bitel.oss.systems.inventory.product.common.bean.Product;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductOffering;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductOfferingActivationMode;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductSpec;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductSpecActivationMode;
import ru.bitel.oss.systems.inventory.product.common.service.ProductService;
import ru.bitel.oss.systems.order.product.common.service.ProductOrderService;

@WebService(endpointInterface = "ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.OrderManagementServiceWS")
public class OrderManagementServiceWSImpl
	extends AbstractService
	implements OrderManagementServiceWS
{
	@Resource
	private TvAccountService tvAccountService;

	@Resource
	private ProductService productService;

	@Resource
	private ProductOrderService productOrderService;

//	private BillingException_Exception newException( String message )
//	{
//		BillingException exception = new BillingException();
//		exception.setMessage( message );
//
//		return new BillingException_Exception( message, exception );
//	}

	private BillingException_Exception newException( String message, Throwable cause )
	{
		BillingException exception = new BillingException();
		exception.setMessage( message );

		return new BillingException_Exception( message, exception, cause );
	}

	public ServiceOrder createOrder( ServiceOffering serviceOffering, int type )
		throws BillingException_Exception
	{
	    getLogger().info( "createOrder()" );
	    getLogger().info( String.valueOf( serviceOffering.getId() ) );
	    getLogger().info( String.valueOf( serviceOffering.getService().getId() ) );
	    getLogger().info( serviceOffering.getService().getName() );
	    getLogger().info( String.valueOf( serviceOffering.getService().getServiceSpecification().getId() ) );
	    getLogger().info( serviceOffering.getService().getServiceSpecification().getName() );
	    getLogger().info( serviceOffering.getService().getServiceSpecification().getType() );

		try
		{
			return createOrderImpl( serviceOffering, type );
		}
		catch( Exception e )
		{
		    getLogger().error( e.getMessage(), e );
			throw newException( e.getMessage(), e );
		}
	}

	private ProductOfferingActivationMode getOfferingActivationMode( final TvAccount tvAccount, int productSpecId, int activationModeId, Date time )
		throws Exception
	{
		List<ProductOffering> productOfferingList =
			productOrderService.productOfferingList( moduleId, tvAccount.getContractId(), tvAccount.getId(), productSpecId, time, true, false );

//		ProductOfferingActivationMode offeringActivationMode = null;
		for( ProductOffering po : productOfferingList )
		{
			if( po.getProductSpec().getId() != productSpecId )
			{
				continue;
			}

			for( ProductOfferingActivationMode poam : po.getActivationModeList() )
			{
				if( poam.getProductSpecActivationMode().getId() != activationModeId )
				{
					continue;
				}

				return poam;
			}
		}

		return null;
	}

	private Product findProduct( TvAccount tvAccount, long ctiServiceId, Date time )
		throws Exception
	{
		List<Product> productList = productService.productList( moduleId, tvAccount.getContractId(), tvAccount.getId(), false, null, null, null, time, false );

		for( Product product : productList )
		{
			if( product.getId() == ctiServiceId || String.valueOf( ctiServiceId ).equals( product.getDeviceProductId() ) )
			{
				return product;
			}
		}

		return null;
	}

	private ServiceOrder createOrderImpl( ServiceOffering serviceOffering, int type )
		throws Exception
	{
		JAXB.marshal( serviceOffering, System.out );

		final Date time = serviceOffering.getDate().toGregorianCalendar().getTime();

		final String accountNumber = serviceOffering.getAccountNumber();
		final TvAccount tvAccount = tvAccountService.tvAccountGetByLogin( accountNumber, time );

		final Service ctiService = serviceOffering.getService();

		final ProductSpec productSpec = productService.productSpecGetByIdentifier( String.valueOf( ctiService.getServiceSpecification().getId() ) );

		final int activationModeId = serviceOffering.getId().intValue();

		ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.Resource resource = serviceOffering.getService().getResource();
		
		Product product = null;

		if( ctiService.getId() != null && ctiService.getId() > 0 )
		{
			product = findProduct( tvAccount,  ctiService.getId(), time );
			if( product != null )
			{
				if( product.getTimeTo() != null )
				{
				    getLogger().info( "Reactivate subscription" );

					productOrderService.productReactivate( tvAccount.getContractId(), product.getId(), false );

					product = productService.productGet( tvAccount.getContractId(), product.getId() );
				}
			}
		}

		if( product == null )
		{
			product = Product.builder()
			    .setContractId( tvAccount.getContractId() )
			    .setAccountId( tvAccount.getId() )
			    .setTimeFrom( serviceOffering.getDate().toGregorianCalendar().getTime() )
			    .setProductSpecId( productSpec.getId() )
			    .setActivationModeId( activationModeId )
			    .setDescription( resource != null ? resource.getName() : "" )
			    .setComment( "Activated from STB" )
			    .build();

			productOrderService.productActivate( product, false, true );
		}

		ServiceOrder ctiOrder = new ServiceOrder();
		ctiOrder.setId( (long)product.getId() );
		ctiOrder.setAccountNumber( accountNumber );
		ctiOrder.setDate( DatatypeFactory.newInstance().newXMLGregorianCalendar( new GregorianCalendar() ) );
		ctiOrder.setService( serviceOffering.getService() );
		ctiOrder.setPrice( serviceOffering.getPrice() );
		ctiOrder.setDescription( "" );

		serviceOffering.getService().getServiceSpecification().setStatus( 1 );

		serviceOffering.getService().setPeriodical( productSpec.isPeriodic() );

		ctiOrder.setType( new OrderType() );

		ctiOrder.getService().setDescription( "" );

		AdditionalParameters additionalParameters = new AdditionalParameters();

		// аренда фильма?
		//if( !productSpec.isPeriodic() )

		ProductOfferingActivationMode offeringActivationMode = getOfferingActivationMode( tvAccount, productSpec.getId(), activationModeId, time );
		ProductSpecActivationMode activationMode = offeringActivationMode.getProductSpecActivationMode();

		int rentTime;
		if( BigDecimal.ZERO.compareTo( offeringActivationMode.getPrice() ) != 0 || BigDecimal.ZERO.compareTo( offeringActivationMode.getActivationPrice() ) == 0 )
		{
			rentTime = (int)TvUtils.periodAmountToUnit( offeringActivationMode.getPeriodMode(), offeringActivationMode.getPeriodAmount(), TimeUnit.HOURS );
		}
		else
		{
			rentTime = (int)TvUtils.periodAmountToUnit( activationMode.getPeriodMode() % 10, activationMode.getPeriodAmount(), TimeUnit.HOURS );
		}

		ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.Service.AdditionalParameters.Entry e = new ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.Service.AdditionalParameters.Entry();
		e.setKey( "rentTime" );
		e.setValue( (short)rentTime );
		additionalParameters.getEntry().add( e );
		ctiService.setAdditionalParameters( additionalParameters );

		TimeFrame timeFrame = new TimeFrame();
		timeFrame.setStartTime( TveUtils.toXmlCalendar( product.getTimeFrom() ) );

		GregorianCalendar calendar = new GregorianCalendar();

		getLogger().info( "SubscriptionTimeTo: " + TimeUtils.format( product.getSubscriptionTimeTo(), "dd.MM.yyyy HH:mm:ss" ) );

		if( product.getSubscriptionTimeTo() != null && product.getSubscriptionTimeTo().after( time ) )
		{
			timeFrame.setEndTime( TveUtils.toXmlCalendar( product.getSubscriptionTimeTo() ) );

			calendar.setTime( product.getTimeFrom() );
			calendar.add( Calendar.HOUR_OF_DAY, rentTime );
		}
		else
		{
			calendar.setTime( product.getTimeFrom() );
			calendar.add( Calendar.HOUR_OF_DAY, rentTime );
		}
		
		if( productSpec.isPeriodic() )
		{
			calendar.add( Calendar.MINUTE, 20 );
		}
		
		timeFrame.setEndTime( DatatypeFactory.newInstance().newXMLGregorianCalendar( calendar ) );

		ctiOrder.getService().setValidFor( timeFrame );

		/*resource = new ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.order.Resource();
		resource.setId( 5555L );
		resource.setDescription( "RESOURCE" );
		resource.setName( "resourceName" );*/

		ctiOrder.getService().setResource( resource );

		/*else
		{
			Entry e = new Entry();
			e.setKey( "price" );
			e.setValue( new BigDecimal( 20 ) );

			additionalParameters.getEntry().add( e );
		}*/

		ctiOrder.getService().setId( (long)product.getId() );

		JAXB.marshal( ctiOrder, System.out );

		return ctiOrder;

	}

	/**
	 * Применяется для услуг, приобретенных по подписке (периодические услуги). Возможно два варианта использования метода:
	 * • абонент подписан на периодическую услугу, но, через некоторое время, решает отказаться от нее (отправляет флаг periodical = FALSE);
	 * • после отказа от подписки он решает возобновить ее, причем текущий период услуги еще не истек (отправляет флаг periodical = TRUE).
	 * Запрос позволяет изменить следующие параметры:
	 * • service -> periodical (флаг автоматического продления периодической услуги)
	 * • service -> price (стоимость за пользование услугой, которая будет изыматься при продлении услуги на очередной период)
	 * • service -> validFor (период действия услуги)
	 */
	public ServiceOrder updateOrder( ServiceOrder serviceOrder )
		throws BillingException_Exception
	{
	    getLogger().info( "updateOrder" );

		JAXB.marshal( serviceOrder, System.out );

		try
		{
			return updateOrderImpl( serviceOrder );
		}
		catch( Exception e )
		{
			throw newException( e.getMessage(), e );
		}
	}

	private ServiceOrder updateOrderImpl( ServiceOrder serviceOrder )
		throws Exception
	{
		final Date time = new Date();//serviceOrder.getDate().toGregorianCalendar().getTime();

		final String accountNumber = serviceOrder.getAccountNumber();
		final TvAccount tvAccount = tvAccountService.tvAccountGetByLogin( accountNumber, time );

		final int productId = serviceOrder.getService().getId().intValue();

		// деактивация
		if( !serviceOrder.getService().isPeriodical() )
		{
		    getLogger().info( "Deactivate subscription" );

			Product product = productService.productGet( tvAccount.getContractId(), productId );
			if( product == null )
			{
				return null;
			}

			productOrderService.productDeactivate( tvAccount.getContractId(), productId, time, true, false );

			product = productService.productGet( tvAccount.getContractId(), productId );
			if( product == null )
			{
				return null;
			}

			// если закрыто в ближайшую минуту - удаляем из MW
			if( product.getTimeTo() != null && TimeUtils.compare( new GregorianCalendar(), TimeUtils.convertDateToCalendar( product.getTimeTo() ), Calendar.MINUTE ) <= 0 )
			{
				return null;
			}

			TimeFrame validFor = serviceOrder.getService().getValidFor();
			validFor.setStartTime( DatatypeFactory.newInstance().newXMLGregorianCalendar( (GregorianCalendar)TimeUtils.convertDateToCalendar( product.getTimeFrom() ) ) );
			validFor.setEndTime( product.getTimeTo() == null ? null : DatatypeFactory.newInstance().newXMLGregorianCalendar( (GregorianCalendar)TimeUtils.convertDateToCalendar( product.getTimeTo() ) ) );

			serviceOrder.getService().setPeriodical( true );

			return serviceOrder;
		}
		// реактивация
		else
		{
		    getLogger().info( "Reactivate subscription" );

			productOrderService.productReactivate( tvAccount.getContractId(), productId, false );

			Product product = productService.productGet( tvAccount.getContractId(), productId );
			if( product == null )
			{
				return null;
			}

			TimeFrame validFor = serviceOrder.getService().getValidFor();
			validFor.setStartTime( DatatypeFactory.newInstance().newXMLGregorianCalendar( (GregorianCalendar)TimeUtils.convertDateToCalendar( product.getTimeFrom() ) ) );
			validFor.setEndTime( product.getTimeTo() == null ? null : DatatypeFactory.newInstance().newXMLGregorianCalendar( (GregorianCalendar)TimeUtils.convertDateToCalendar( product.getTimeTo() ) ) );

			return serviceOrder;
		}
	}

	public ServiceOrder createOrderByOffering( ServiceOffering serviceOffering )
		throws BillingException_Exception
	{
	    getLogger().info( "createOrderByOffering" );
		return null;
	}

	public List<ServiceOrder> findActiveOrders( @WebParam(name = "arg0", targetNamespace = "") ServiceSpecification arg0, @WebParam(name = "arg1", targetNamespace = "") String arg1, @WebParam(name = "arg2", targetNamespace = "") int arg2 )
		throws BillingException_Exception
	{
	    getLogger().info( "findActiveOrders" );
		return null;
	}

	public List<ServiceOrder> findActiveOrdersBySSAndAN( ServiceSpecification serviceSpecification, String accountNumber )
		throws BillingException_Exception
	{
	    getLogger().info( "findActiveOrdersBySSAndAN" );
		return null;
	}

	public List<ServiceOrder> findOrdersBySAndAN( ServiceSpecification serviceSpecification, String accountNumber, TimeFrame timeFrame )
		throws BillingException_Exception
	{
	    getLogger().info( "findOrdersBySAndAN" );
		return null;
	}

	public List<ServiceOrder> findOrders( ServiceSpecification serviceSpecification, String accountNumber, int arg2, TimeFrame timeFrame )
		throws BillingException_Exception
	{
	    getLogger().info( "findOrders" );
		return null;
	}
}
