﻿using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using EasyApi.Metadata;
using EasyApi.Reflection;
using EasyApi.Utils;
using EasyApi.Values;

namespace EasyApi.Formatting
{
	public abstract class ContentFormatter : IRequestContentFormatter, IResponseContentFormatter
	{
		protected static Exception ValueTypeIsNotSupportedException(IValueMetadata valueMetadata)
		{
			Argument.IsNotNull(valueMetadata, nameof(valueMetadata));

			return new InvalidOperationException("Type of the value is not supported.")
				.SetData("ValueType", valueMetadata.ValueType);
		}

		protected abstract HttpContent Serialize(IValue value, IActionMetadata actionMetadata);

		protected abstract void Deserialize(IValue value, HttpContent content, IActionMetadata actionMetadata);

		protected abstract Task DeserializeAsync(IValue value, HttpContent content, IActionMetadata actionMetadata);

		protected virtual bool ValueTypeIsSupported(Type valueType)
		{
			return !valueType.IsVoid();
		}

		protected void EnsureValueIsSupported(IValueMetadata valueMetadata)
		{
			Argument.IsNotNull(valueMetadata, nameof(valueMetadata));

			if (!ValueTypeIsSupported(valueMetadata.ValueType))
			{
				throw ValueTypeIsNotSupportedException(valueMetadata);
			}
		}

		protected virtual HttpContent Serialize(ActionArgument argument, IActionMetadata actionMetadata)
		{
			Argument.IsNotNull(argument, nameof(argument));
			Argument.IsNotNull(actionMetadata, nameof(actionMetadata));

			EnsureValueIsSupported(argument.Metadata);

			return Serialize((IValue)argument, actionMetadata);
		}

		HttpContent IRequestContentFormatter.Serialize(ActionArgument argument, IActionMetadata actionMetadata)
		{
			return Serialize(argument, actionMetadata);
		}

		protected virtual Task DeserializeAsync(ActionArgument argument, HttpContent content, IActionMetadata actionMetadata)
		{
			Argument.IsNotNull(content, nameof(content));
			Argument.IsNotNull(argument, nameof(argument));
			Argument.IsNotNull(actionMetadata, nameof(actionMetadata));

			EnsureValueIsSupported(argument.Metadata);

			return DeserializeAsync((IValue)argument, content, actionMetadata);
		}

		Task IRequestContentFormatter.DeserializeAsync(ActionArgument argument, HttpContent content, IActionMetadata actionMetadata)
		{
			return DeserializeAsync(argument, content, actionMetadata);
		}

		protected virtual HttpContent Serialize(ActionResult result, HttpRequestMessage request, IActionMetadata actionMetadata)
		{
			Argument.IsNotNull(result, nameof(result));
			Argument.IsNotNull(request, nameof(request));
			Argument.IsNotNull(actionMetadata, nameof(actionMetadata));

			EnsureValueIsSupported(result.Metadata);

			return Serialize(result, actionMetadata);
		}

		HttpContent IResponseContentFormatter.Serialize(ActionResult result, HttpRequestMessage request, IActionMetadata actionMetadata)
		{
			return Serialize(result, request, actionMetadata);
		}

		protected virtual void PrepareRequest(HttpRequestMessage request, IResultMetadata resultMetadata, IActionMetadata actionMetadata)
		{
			Argument.IsNotNull(request, nameof(request));
			Argument.IsNotNull(resultMetadata, nameof(resultMetadata));
			Argument.IsNotNull(actionMetadata, nameof(actionMetadata));

			EnsureValueIsSupported(resultMetadata);

			PrepareRequestHeaders(request.Headers, resultMetadata, actionMetadata);
		}

		protected virtual void PrepareRequestHeaders(HttpRequestHeaders headers, IResultMetadata resultMetadata, IActionMetadata actionMetadata)
		{
		}

		void IResponseContentFormatter.PrepareRequest(HttpRequestMessage request, IResultMetadata resultMetadata, IActionMetadata actionMetadata)
		{
			PrepareRequest(request, resultMetadata, actionMetadata);
		}

		protected virtual void Deserialize(ActionResult result, HttpContent content, IActionMetadata actionMetadata)
		{
			Argument.IsNotNull(content, nameof(content));
			Argument.IsNotNull(result, nameof(result));
			Argument.IsNotNull(actionMetadata, nameof(actionMetadata));

			EnsureValueIsSupported(result.Metadata);

			Deserialize((IValue)result, content, actionMetadata);
		}

		void IResponseContentFormatter.Deserialize(ActionResult result, HttpContent content, IActionMetadata actionMetadata)
		{
			Deserialize(result, content, actionMetadata);
		}

		protected virtual Task DeserializeAsync(ActionResult result, HttpContent content, IActionMetadata actionMetadata)
		{
			Argument.IsNotNull(content, nameof(content));
			Argument.IsNotNull(result, nameof(result));
			Argument.IsNotNull(actionMetadata, nameof(actionMetadata));

			EnsureValueIsSupported(result.Metadata);

			return DeserializeAsync((IValue)result, content, actionMetadata);
		}

		Task IResponseContentFormatter.DeserializeAsync(ActionResult result, HttpContent content, IActionMetadata actionMetadata)
		{
			return DeserializeAsync(result, content, actionMetadata);
		}
	}
}