use anyhow::Context;
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use surrealdb_protocol::fb::v1 as proto_fb;

use super::{FromFlatbuffers, ToFlatbuffers};
use crate::{Bytes, Duration, Uuid};

impl ToFlatbuffers for bool {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::BoolValue<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		Ok(proto_fb::BoolValue::create(
			builder,
			&proto_fb::BoolValueArgs {
				value: *self,
			},
		))
	}
}

impl FromFlatbuffers for bool {
	type Input<'a> = proto_fb::BoolValue<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		Ok(input.value())
	}
}

impl ToFlatbuffers for i64 {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::Int64Value<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		Ok(proto_fb::Int64Value::create(
			builder,
			&proto_fb::Int64ValueArgs {
				value: *self,
			},
		))
	}
}

impl FromFlatbuffers for i64 {
	type Input<'a> = proto_fb::Int64Value<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		Ok(input.value())
	}
}

impl ToFlatbuffers for u64 {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::UInt64Value<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		Ok(proto_fb::UInt64Value::create(
			builder,
			&proto_fb::UInt64ValueArgs {
				value: *self,
			},
		))
	}
}

impl FromFlatbuffers for u64 {
	type Input<'a> = proto_fb::UInt64Value<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		Ok(input.value())
	}
}

impl ToFlatbuffers for f64 {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::Float64Value<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		Ok(proto_fb::Float64Value::create(
			builder,
			&proto_fb::Float64ValueArgs {
				value: *self,
			},
		))
	}
}

impl FromFlatbuffers for f64 {
	type Input<'a> = proto_fb::Float64Value<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		Ok(input.value())
	}
}

impl ToFlatbuffers for String {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::StringValue<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		let value = builder.create_string(self);
		Ok(proto_fb::StringValue::create(
			builder,
			&proto_fb::StringValueArgs {
				value: Some(value),
			},
		))
	}
}

impl FromFlatbuffers for String {
	type Input<'a> = proto_fb::StringValue<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		let Some(value) = input.value() else {
			return Err(anyhow::anyhow!("Missing string value"));
		};
		Ok(value.to_string())
	}
}

impl ToFlatbuffers for Decimal {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::Decimal<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		let value = builder.create_string(&self.to_string());
		Ok(proto_fb::Decimal::create(
			builder,
			&proto_fb::DecimalArgs {
				value: Some(value),
			},
		))
	}
}

impl FromFlatbuffers for Decimal {
	type Input<'a> = proto_fb::Decimal<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		let Some(value_str) = input.value() else {
			return Err(anyhow::anyhow!("Missing decimal string"));
		};
		value_str.parse::<Decimal>().context("Failed to parse decimal")
	}
}

impl ToFlatbuffers for std::time::Duration {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::Duration<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		Ok(proto_fb::Duration::create(
			builder,
			&proto_fb::DurationArgs {
				seconds: self.as_secs(),
				nanos: self.subsec_nanos(),
			},
		))
	}
}

impl FromFlatbuffers for std::time::Duration {
	type Input<'a> = proto_fb::Duration<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		let seconds = input.seconds();
		let nanos = input.nanos();
		Ok(std::time::Duration::new(seconds, nanos))
	}
}

impl ToFlatbuffers for Duration {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::Duration<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		self.0.to_fb(builder)
	}
}

impl FromFlatbuffers for Duration {
	type Input<'a> = proto_fb::Duration<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		let duration = std::time::Duration::from_fb(input)?;
		Ok(Duration(duration))
	}
}

impl ToFlatbuffers for DateTime<Utc> {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::Timestamp<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		Ok(proto_fb::Timestamp::create(
			builder,
			&proto_fb::TimestampArgs {
				seconds: self.timestamp(),
				nanos: self.timestamp_subsec_nanos(),
			},
		))
	}
}

impl FromFlatbuffers for DateTime<Utc> {
	type Input<'a> = proto_fb::Timestamp<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		let seconds = input.seconds();
		let nanos = input.nanos();
		DateTime::<Utc>::from_timestamp(seconds, nanos)
			.ok_or_else(|| anyhow::anyhow!("Invalid timestamp format"))
	}
}

impl ToFlatbuffers for Uuid {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::Uuid<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		let bytes = builder.create_vector(self.0.as_bytes());
		Ok(proto_fb::Uuid::create(
			builder,
			&proto_fb::UuidArgs {
				bytes: Some(bytes),
			},
		))
	}
}

impl FromFlatbuffers for Uuid {
	type Input<'a> = proto_fb::Uuid<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		let bytes_vector = input.bytes().ok_or_else(|| anyhow::anyhow!("Missing bytes in Uuid"))?;
		uuid::Uuid::from_slice(bytes_vector.bytes())
			.map(Uuid)
			.map_err(|_| anyhow::anyhow!("Invalid UUID format"))
	}
}

impl ToFlatbuffers for Bytes {
	type Output<'bldr> = flatbuffers::WIPOffset<proto_fb::Bytes<'bldr>>;

	#[inline]
	fn to_fb<'bldr>(
		&self,
		builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
	) -> anyhow::Result<Self::Output<'bldr>> {
		let data = builder.create_vector(&self.0);
		Ok(proto_fb::Bytes::create(
			builder,
			&proto_fb::BytesArgs {
				value: Some(data),
			},
		))
	}
}

impl FromFlatbuffers for Bytes {
	type Input<'a> = proto_fb::Bytes<'a>;

	#[inline]
	fn from_fb(input: Self::Input<'_>) -> anyhow::Result<Self> {
		let data = input.value().ok_or_else(|| anyhow::anyhow!("Missing value in Bytes"))?;
		Ok(Bytes(::bytes::Bytes::copy_from_slice(data.bytes())))
	}
}
