#include "duckdb/main/capi/capi_internal.hpp"
#include "duckdb/common/uhugeint.hpp"

using duckdb::Appender;
using duckdb::AppenderWrapper;
using duckdb::BaseAppender;
using duckdb::Connection;
using duckdb::date_t;
using duckdb::dtime_t;
using duckdb::ErrorData;
using duckdb::ErrorDataWrapper;
using duckdb::hugeint_t;
using duckdb::interval_t;
using duckdb::string_t;
using duckdb::timestamp_t;
using duckdb::uhugeint_t;

duckdb_state duckdb_appender_create(duckdb_connection connection, const char *schema, const char *table,
                                    duckdb_appender *out_appender) {
	return duckdb_appender_create_ext(connection, INVALID_CATALOG, schema, table, out_appender);
}

duckdb_state duckdb_appender_create_ext(duckdb_connection connection, const char *catalog, const char *schema,
                                        const char *table, duckdb_appender *out_appender) {
	Connection *conn = reinterpret_cast<Connection *>(connection);

	if (!connection || !table || !out_appender) {
		return DuckDBError;
	}
	if (catalog == nullptr) {
		catalog = INVALID_CATALOG;
	}
	if (schema == nullptr) {
		schema = DEFAULT_SCHEMA;
	}

	auto wrapper = new AppenderWrapper();
	*out_appender = reinterpret_cast<duckdb_appender>(wrapper);
	try {
		wrapper->appender = duckdb::make_uniq<Appender>(*conn, catalog, schema, table);
	} catch (std::exception &ex) {
		wrapper->error_data = ErrorData(ex);
		return DuckDBError;
	} catch (...) { // LCOV_EXCL_START
		wrapper->error_data = ErrorData("Unknown create appender error");
		return DuckDBError;
	} // LCOV_EXCL_STOP
	return DuckDBSuccess;
}

duckdb_state duckdb_appender_create_query(duckdb_connection connection, const char *query, idx_t column_count,
                                          duckdb_logical_type *types_p, const char *table_name_p,
                                          const char **column_names_p, duckdb_appender *out_appender) {
	Connection *conn = reinterpret_cast<Connection *>(connection);

	if (!connection || !query || !column_count || !types_p) {
		return DuckDBError;
	}
	duckdb::vector<duckdb::LogicalType> types;
	duckdb::vector<duckdb::string> column_names;
	duckdb::string table_name;
	for (idx_t c = 0; c < column_count; ++c) {
		if (!types_p[c]) {
			return DuckDBError;
		}
		types.push_back(*reinterpret_cast<duckdb::LogicalType *>(types_p[c]));
	}
	if (table_name_p) {
		table_name = table_name_p;
	}
	if (column_names_p) {
		for (idx_t c = 0; c < column_count; ++c) {
			if (!column_names_p[c]) {
				return DuckDBError;
			}
			column_names.push_back(column_names_p[c]);
		}
	}

	auto wrapper = new AppenderWrapper();
	*out_appender = reinterpret_cast<duckdb_appender>(wrapper);
	try {
		wrapper->appender = duckdb::make_uniq<duckdb::QueryAppender>(*conn, query, std::move(types),
		                                                             std::move(column_names), std::move(table_name));
	} catch (std::exception &ex) {
		wrapper->error_data = ErrorData(ex);
		return DuckDBError;
	} catch (...) { // LCOV_EXCL_START
		wrapper->error_data = ErrorData("Unknown create appender error");
		return DuckDBError;
	} // LCOV_EXCL_STOP
	return DuckDBSuccess;
}

duckdb_state duckdb_appender_destroy(duckdb_appender *appender) {
	if (!appender || !*appender) {
		return DuckDBError;
	}
	auto state = duckdb_appender_close(*appender);
	auto wrapper = reinterpret_cast<AppenderWrapper *>(*appender);
	if (wrapper) {
		delete wrapper;
	}
	*appender = nullptr;
	return state;
}

template <class FUN>
duckdb_state duckdb_appender_run_function(duckdb_appender appender, FUN &&function) {
	if (!appender) {
		return DuckDBError;
	}
	auto wrapper = reinterpret_cast<AppenderWrapper *>(appender);
	if (!wrapper->appender) {
		wrapper->error_data = ErrorData("not a valid appender");
		return DuckDBError;
	}
	try {
		function(*wrapper->appender);
	} catch (std::exception &ex) {
		wrapper->error_data = ErrorData(ex);
		return DuckDBError;
	} catch (...) { // LCOV_EXCL_START
		wrapper->error_data = ErrorData("Unknown appender error");
		return DuckDBError;
	} // LCOV_EXCL_STOP
	return DuckDBSuccess;
}

duckdb_state duckdb_appender_add_column(duckdb_appender appender_p, const char *name) {
	return duckdb_appender_run_function(appender_p, [&](BaseAppender &appender) { appender.AddColumn(name); });
}

duckdb_state duckdb_appender_clear_columns(duckdb_appender appender_p) {
	return duckdb_appender_run_function(appender_p, [&](BaseAppender &appender) { appender.ClearColumns(); });
}

const char *duckdb_appender_error(duckdb_appender appender) {
	if (!appender) {
		return nullptr;
	}
	auto wrapper = reinterpret_cast<AppenderWrapper *>(appender);
	if (!wrapper->error_data.HasError()) {
		return nullptr;
	}
	return wrapper->error_data.RawMessage().c_str();
}

duckdb_error_data duckdb_appender_error_data(duckdb_appender appender) {
	auto errorDataWrapper = new ErrorDataWrapper();
	if (!appender) {
		return reinterpret_cast<duckdb_error_data>(errorDataWrapper);
	}

	auto appenderWrapper = reinterpret_cast<AppenderWrapper *>(appender);
	errorDataWrapper->error_data = appenderWrapper->error_data;
	return reinterpret_cast<duckdb_error_data>(errorDataWrapper);
}

duckdb_state duckdb_appender_begin_row(duckdb_appender appender) {
	return DuckDBSuccess;
}

duckdb_state duckdb_appender_end_row(duckdb_appender appender_p) {
	return duckdb_appender_run_function(appender_p, [&](BaseAppender &appender) { appender.EndRow(); });
}

template <class T>
duckdb_state duckdb_append_internal(duckdb_appender appender, T value) {
	if (!appender) {
		return DuckDBError;
	}
	auto *appender_instance = reinterpret_cast<AppenderWrapper *>(appender);
	try {
		appender_instance->appender->Append<T>(value);
	} catch (std::exception &ex) {
		appender_instance->error_data = ErrorData(ex);
		return DuckDBError;
	} catch (...) {
		return DuckDBError;
	}
	return DuckDBSuccess;
}

duckdb_state duckdb_append_default(duckdb_appender appender) {
	if (!appender) {
		return DuckDBError;
	}
	auto *appender_instance = reinterpret_cast<AppenderWrapper *>(appender);

	try {
		appender_instance->appender->AppendDefault();
	} catch (std::exception &ex) {
		appender_instance->error_data = ErrorData(ex);
		return DuckDBError;
	} catch (...) {
		return DuckDBError;
	}
	return DuckDBSuccess;
}

duckdb_state duckdb_append_default_to_chunk(duckdb_appender appender, duckdb_data_chunk chunk, idx_t col, idx_t row) {
	if (!appender || !chunk) {
		return DuckDBError;
	}

	auto *appender_instance = reinterpret_cast<AppenderWrapper *>(appender);

	auto data_chunk = reinterpret_cast<duckdb::DataChunk *>(chunk);

	try {
		appender_instance->appender->AppendDefault(*data_chunk, col, row);
	} catch (std::exception &ex) {
		appender_instance->error_data = ErrorData(ex);
		return DuckDBError;
	} catch (...) {
		return DuckDBError;
	}
	return DuckDBSuccess;
}

duckdb_state duckdb_append_bool(duckdb_appender appender, bool value) {
	return duckdb_append_internal<bool>(appender, value);
}

duckdb_state duckdb_append_int8(duckdb_appender appender, int8_t value) {
	return duckdb_append_internal<int8_t>(appender, value);
}

duckdb_state duckdb_append_int16(duckdb_appender appender, int16_t value) {
	return duckdb_append_internal<int16_t>(appender, value);
}

duckdb_state duckdb_append_int32(duckdb_appender appender, int32_t value) {
	return duckdb_append_internal<int32_t>(appender, value);
}

duckdb_state duckdb_append_int64(duckdb_appender appender, int64_t value) {
	return duckdb_append_internal<int64_t>(appender, value);
}

duckdb_state duckdb_append_hugeint(duckdb_appender appender, duckdb_hugeint value) {
	hugeint_t internal;
	internal.lower = value.lower;
	internal.upper = value.upper;
	return duckdb_append_internal<hugeint_t>(appender, internal);
}

duckdb_state duckdb_append_uint8(duckdb_appender appender, uint8_t value) {
	return duckdb_append_internal<uint8_t>(appender, value);
}

duckdb_state duckdb_append_uint16(duckdb_appender appender, uint16_t value) {
	return duckdb_append_internal<uint16_t>(appender, value);
}

duckdb_state duckdb_append_uint32(duckdb_appender appender, uint32_t value) {
	return duckdb_append_internal<uint32_t>(appender, value);
}

duckdb_state duckdb_append_uint64(duckdb_appender appender, uint64_t value) {
	return duckdb_append_internal<uint64_t>(appender, value);
}

duckdb_state duckdb_append_uhugeint(duckdb_appender appender, duckdb_uhugeint value) {
	uhugeint_t internal;
	internal.lower = value.lower;
	internal.upper = value.upper;
	return duckdb_append_internal<uhugeint_t>(appender, internal);
}

duckdb_state duckdb_append_float(duckdb_appender appender, float value) {
	return duckdb_append_internal<float>(appender, value);
}

duckdb_state duckdb_append_double(duckdb_appender appender, double value) {
	return duckdb_append_internal<double>(appender, value);
}

duckdb_state duckdb_append_date(duckdb_appender appender, duckdb_date value) {
	return duckdb_append_internal<date_t>(appender, date_t(value.days));
}

duckdb_state duckdb_append_time(duckdb_appender appender, duckdb_time value) {
	return duckdb_append_internal<dtime_t>(appender, dtime_t(value.micros));
}

duckdb_state duckdb_append_timestamp(duckdb_appender appender, duckdb_timestamp value) {
	return duckdb_append_internal<timestamp_t>(appender, timestamp_t(value.micros));
}

duckdb_state duckdb_append_interval(duckdb_appender appender, duckdb_interval value) {
	interval_t interval;
	interval.months = value.months;
	interval.days = value.days;
	interval.micros = value.micros;
	return duckdb_append_internal<interval_t>(appender, interval);
}

duckdb_state duckdb_append_null(duckdb_appender appender) {
	return duckdb_append_internal<std::nullptr_t>(appender, nullptr);
}

duckdb_state duckdb_append_varchar(duckdb_appender appender, const char *val) {
	return duckdb_append_internal<const char *>(appender, val);
}

duckdb_state duckdb_append_varchar_length(duckdb_appender appender, const char *val, idx_t length) {
	return duckdb_append_internal<string_t>(appender, string_t(val, duckdb::UnsafeNumericCast<uint32_t>(length)));
}

duckdb_state duckdb_append_blob(duckdb_appender appender, const void *data, idx_t length) {
	auto value = duckdb::Value::BLOB(duckdb::const_data_ptr_cast(data), length);
	return duckdb_append_internal<duckdb::Value>(appender, value);
}

duckdb_state duckdb_appender_flush(duckdb_appender appender_p) {
	return duckdb_appender_run_function(appender_p, [&](BaseAppender &appender) { appender.Flush(); });
}

duckdb_state duckdb_appender_clear(duckdb_appender appender_p) {
	return duckdb_appender_run_function(appender_p, [&](BaseAppender &appender) { appender.Clear(); });
}

duckdb_state duckdb_appender_close(duckdb_appender appender_p) {
	return duckdb_appender_run_function(appender_p, [&](BaseAppender &appender) { appender.Close(); });
}

idx_t duckdb_appender_column_count(duckdb_appender appender) {
	if (!appender) {
		return 0;
	}

	auto wrapper = reinterpret_cast<AppenderWrapper *>(appender);
	if (!wrapper->appender) {
		return 0;
	}

	return wrapper->appender->GetActiveTypes().size();
}

duckdb_logical_type duckdb_appender_column_type(duckdb_appender appender, idx_t col_idx) {
	if (!appender || col_idx >= duckdb_appender_column_count(appender)) {
		return nullptr;
	}

	auto wrapper = reinterpret_cast<AppenderWrapper *>(appender);
	if (!wrapper->appender) {
		return nullptr;
	}

	auto &logical_type = wrapper->appender->GetActiveTypes()[col_idx];
	return reinterpret_cast<duckdb_logical_type>(new duckdb::LogicalType(logical_type));
}

duckdb_state duckdb_append_value(duckdb_appender appender, duckdb_value value) {
	return duckdb_append_internal<duckdb::Value>(appender, *(reinterpret_cast<duckdb::Value *>(value)));
}

duckdb_state duckdb_append_data_chunk(duckdb_appender appender_p, duckdb_data_chunk chunk) {
	if (!chunk) {
		return DuckDBError;
	}
	auto data_chunk = reinterpret_cast<duckdb::DataChunk *>(chunk);
	return duckdb_appender_run_function(appender_p,
	                                    [&](BaseAppender &appender) { appender.AppendDataChunk(*data_chunk); });
}
