package com.tqdev.crudapi.controller;

import java.util.ArrayList;
import java.util.LinkedHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.tqdev.crudapi.record.RecordService;
import com.tqdev.crudapi.record.container.Record;
import com.tqdev.crudapi.record.ErrorCode;
import com.tqdev.crudapi.record.Params;

@RestController
@RequestMapping("/records")
public class RecordController {

	public static final Logger logger = LoggerFactory.getLogger(RecordController.class);

	@Autowired
	Responder responder;

	@Autowired
	RecordService service;

	@RequestMapping(value = "/{table}", method = RequestMethod.GET)
	public ResponseEntity<?> list(@PathVariable("table") String table,
			@RequestParam LinkedMultiValueMap<String, String> params) {
		logger.info("Listing table with name {} and parameters {}", table, params);
		if (!service.exists(table)) {
			return responder.error(ErrorCode.TABLE_NOT_FOUND, table);
		}
		return responder.success(service.list(table, new Params(params)));
	}

	@RequestMapping(value = "/{table}/{id}", method = RequestMethod.GET)
	public ResponseEntity<?> read(@PathVariable("table") String table, @PathVariable("id") String id,
			@RequestParam LinkedMultiValueMap<String, String> params) {
		logger.info("Reading record from {} with id {} and parameters {}", table, id, params);
		if (!service.exists(table)) {
			return responder.error(ErrorCode.TABLE_NOT_FOUND, table);
		}
		if (id.indexOf(',') >= 0) {
			String[] ids = id.split(",");
			ArrayList<Object> result = new ArrayList<>();
			for (int i = 0; i < ids.length; i++) {
				result.add(service.read(table, ids[i], new Params(params)));
			}
			return responder.success(result);
		} else {
			Object response = service.read(table, id, new Params(params));
			if (response == null) {
				return responder.error(ErrorCode.RECORD_NOT_FOUND, id);
			}
			return responder.success(response);
		}
	}

	@RequestMapping(value = "/{table}", method = RequestMethod.POST, headers = "Content-Type=application/x-www-form-urlencoded")
	public ResponseEntity<?> create(@PathVariable("table") String table,
			@RequestBody LinkedMultiValueMap<String, String> record,
			@RequestParam LinkedMultiValueMap<String, String> params) {
		ObjectMapper mapper = new ObjectMapper();
		Object pojo = mapper.convertValue(convertToSingleValueMap(record), Object.class);
		return create(table, pojo, params);
	}

	@RequestMapping(value = "/{table}", method = RequestMethod.POST, headers = "Content-Type=application/json")
	public ResponseEntity<?> create(@PathVariable("table") String table, @RequestBody Object record,
			@RequestParam LinkedMultiValueMap<String, String> params) {
		logger.info("Creating record in {} with properties {}", table, record);
		if (!service.exists(table)) {
			return responder.error(ErrorCode.TABLE_NOT_FOUND, table);
		}
		if (record instanceof ArrayList<?>) {
			ArrayList<?> records = (ArrayList<?>) record;
			ArrayList<Object> result = new ArrayList<>();
			for (int i = 0; i < records.size(); i++) {
				result.add(service.create(table, Record.valueOf(records.get(i)), new Params(params)));
			}
			return responder.success(result);
		} else {
			return responder.success(service.create(table, Record.valueOf(record), new Params(params)));
		}
	}

	@SuppressWarnings("unchecked")
	private LinkedHashMap<String, Object> convertToSingleValueMap(LinkedMultiValueMap<String, String> map) {
		LinkedHashMap<String, Object> result = new LinkedHashMap<>();
		for (String key : map.keySet()) {
			for (String v : map.get(key)) {
				Object value = v;
				if (key.endsWith("__is_null")) {
					key = key.substring(0, key.indexOf("__is_null"));
					value = null;
				}
				if (result.containsKey(key)) {
					Object current = result.get(key);
					if (current.getClass().isArray()) {
						((ArrayList<Object>) current).add(value);
					} else {
						ArrayList<Object> arr = new ArrayList<>();
						arr.add(current);
						arr.add(v);
						value = arr;
					}
				}
				result.put(key, value);
			}
		}
		return result;
	}

	@RequestMapping(value = "/{table}/{id}", method = RequestMethod.PUT, headers = "Content-Type=application/x-www-form-urlencoded")
	public ResponseEntity<?> update(@PathVariable("table") String table, @PathVariable("id") String id,
			@RequestBody LinkedMultiValueMap<String, String> record,
			@RequestParam LinkedMultiValueMap<String, String> params) {
		ObjectMapper mapper = new ObjectMapper();
		Object pojo = mapper.convertValue(convertToSingleValueMap(record), Object.class);
		return update(table, id, pojo, params);
	}

	@RequestMapping(value = "/{table}/{id}", method = RequestMethod.PUT, headers = "Content-Type=application/json")
	public ResponseEntity<?> update(@PathVariable("table") String table, @PathVariable("id") String id,
			@RequestBody Object record, @RequestParam LinkedMultiValueMap<String, String> params) {
		logger.info("Inrementing record in {} with id {} and properties {}", table, id, record);
		if (!service.exists(table)) {
			return responder.error(ErrorCode.TABLE_NOT_FOUND, table);
		}
		String[] ids = id.split(",");
		if (record instanceof ArrayList<?>) {
			ArrayList<?> records = (ArrayList<?>) record;
			if (ids.length != records.size()) {
				return responder.error(ErrorCode.ARGUMENT_COUNT_MISMATCH, id);
			}
			ArrayList<Object> result = new ArrayList<>();
			for (int i = 0; i < ids.length; i++) {
				result.add(service.update(table, ids[i], Record.valueOf(records.get(i)), new Params(params)));
			}
			return responder.success(result);
		} else {
			if (ids.length != 1) {
				return responder.error(ErrorCode.ARGUMENT_COUNT_MISMATCH, id);
			}
			return responder.success(service.update(table, id, Record.valueOf(record), new Params(params)));
		}
	}

	@RequestMapping(value = "/{table}/{id}", method = RequestMethod.PATCH, headers = "Content-Type=application/x-www-form-urlencoded")
	public ResponseEntity<?> increment(@PathVariable("table") String table, @PathVariable("id") String id,
			@RequestBody LinkedMultiValueMap<String, String> record,
			@RequestParam LinkedMultiValueMap<String, String> params) {
		ObjectMapper mapper = new ObjectMapper();
		Object pojo = mapper.convertValue(convertToSingleValueMap(record), Object.class);
		return update(table, id, pojo, params);
	}

	@RequestMapping(value = "/{table}/{id}", method = RequestMethod.PATCH, headers = "Content-Type=application/json")
	public ResponseEntity<?> increment(@PathVariable("table") String table, @PathVariable("id") String id,
			@RequestBody Object record, @RequestParam LinkedMultiValueMap<String, String> params) {
		logger.info("Updating record in {} with id {} and properties {}", table, id, record);
		if (!service.exists(table)) {
			return responder.error(ErrorCode.TABLE_NOT_FOUND, table);
		}
		String[] ids = id.split(",");
		if (record instanceof ArrayList<?>) {
			ArrayList<?> records = (ArrayList<?>) record;
			if (ids.length != records.size()) {
				return responder.error(ErrorCode.ARGUMENT_COUNT_MISMATCH, id);
			}
			ArrayList<Object> result = new ArrayList<>();
			for (int i = 0; i < ids.length; i++) {
				result.add(service.increment(table, ids[i], Record.valueOf(records.get(i)), new Params(params)));
			}
			return responder.success(result);
		} else {
			if (ids.length != 1) {
				return responder.error(ErrorCode.ARGUMENT_COUNT_MISMATCH, id);
			}
			return responder.success(service.increment(table, id, Record.valueOf(record), new Params(params)));
		}
	}

	@RequestMapping(value = "/{table}/{id}", method = RequestMethod.DELETE)
	public ResponseEntity<?> delete(@PathVariable("table") String table, @PathVariable("id") String id,
			@RequestParam LinkedMultiValueMap<String, String> params) {
		logger.info("Deleting record from {} with id {}", table, id);
		if (!service.exists(table)) {
			return responder.error(ErrorCode.TABLE_NOT_FOUND, table);
		}
		String[] ids = id.split(",");
		if (ids.length > 1) {
			ArrayList<Object> result = new ArrayList<>();
			for (int i = 0; i < ids.length; i++) {
				result.add(service.delete(table, ids[i], new Params(params)));
			}
			return responder.success(result);
		} else {
			return responder.success(service.delete(table, id, new Params(params)));
		}
	}

}