package com.ffii.fpsms.modules.user.web;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.ffii.fpsms.modules.user.entity.projections.UserCombo;
import com.ffii.fpsms.modules.user.service.pojo.UserRecord;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.ffii.core.exception.BadRequestException;
import com.ffii.core.exception.NotFoundException;
import com.ffii.core.exception.UnprocessableEntityException;
import com.ffii.core.response.IdRes;
import com.ffii.core.utils.PasswordUtils;
import com.ffii.fpsms.modules.common.ErrorCodes;
import com.ffii.fpsms.modules.common.PasswordRule;
import com.ffii.fpsms.modules.common.SecurityUtils;
import com.ffii.fpsms.modules.settings.service.SettingsService;
import com.ffii.fpsms.modules.user.entity.User;
import com.ffii.fpsms.modules.user.req.NewPublicUserReq;
import com.ffii.fpsms.modules.user.req.NewUserReq;
import com.ffii.fpsms.modules.user.req.SearchUserReq;
import com.ffii.fpsms.modules.user.req.UpdateUserReq;
import com.ffii.fpsms.modules.user.service.UserService;
import com.ffii.fpsms.modules.user.service.res.LoadUserRes;

import com.ffii.fpsms.modules.user.service.UserQrCodeService;
import jakarta.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperPrint;
import java.io.OutputStream;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;

@RestController
@RequestMapping("/user")
public class UserController{

    private final Log logger = LogFactory.getLog(getClass());
    private UserService userService;
	private PasswordEncoder passwordEncoder;
	private SettingsService settingsService;
	private UserQrCodeService userQrCodeService;

    public UserController(
		UserService userService,
		PasswordEncoder passwordEncoder,
		SettingsService settingsService,
		UserQrCodeService userQrCodeService
        ) {
        this.userService = userService;
		this.passwordEncoder = passwordEncoder;
		this.settingsService = settingsService;
		this.userQrCodeService = userQrCodeService;
	}
    
    // @Operation(summary = "list user", responses = { @ApiResponse(responseCode = "200"),
	// 		@ApiResponse(responseCode = "404", content = @Content) })
	@GetMapping
//	@PreAuthorize("hasAuthority('VIEW_USER')")
	public ResponseEntity<List<UserRecord>> list(@ModelAttribute @Valid SearchUserReq req) {
        return ResponseEntity.ok(userService.search(req));
	}

	@GetMapping("/escalation-combo")
	public ResponseEntity<List<UserCombo>> escalationCombo() {
		return ResponseEntity.ok(userService.getEscalationCombo());
	}

	@GetMapping("/name-list")
	public ResponseEntity<List<Map<String, Object>>> namelist() {
		SearchUserReq req = new SearchUserReq();
		List<Map<String, Object>> namelist = userService.search(req).stream()
				.map(user -> {
					Map<String, Object> map = new HashMap<>();
					map.put("id", user.getId());
					map.put("name", user.getName());
					return map;
				})
				.toList();
		return ResponseEntity.ok(namelist);
	}
	@GetMapping("/new-name-list")
	public ResponseEntity<List<Map<String, Object>>> newnamelist() {
		SearchUserReq req = new SearchUserReq();
		List<Map<String, Object>> newnamelist = userService.search(req).stream()
				.map(user -> {
					Map<String, Object> map = new HashMap<>();
					map.put("id", user.getId());
					map.put("name", user.getName());
					map.put("title", user.getTitle());
					map.put("department", user.getDepartment());
					return map;
				})
				.toList();
		return ResponseEntity.ok(newnamelist);
	}
    // @Operation(summary = "load user data", responses = { @ApiResponse(responseCode = "200"),
	// 		@ApiResponse(responseCode = "404", content = @Content) })
	@GetMapping("/{id}")
	@PreAuthorize("hasAuthority('VIEW_USER')")
	public LoadUserRes load(@PathVariable long id) {
		return new LoadUserRes(
											userService.find(id).orElseThrow(NotFoundException::new),
											userService.listUserAuthId(id),
											userService.listUserGroupId(id));
	}
	@GetMapping("/user-info/{id}")
//	@PreAuthorize("hasAuthority('VIEW_USER')")
	public ResponseEntity<?> getUserById(@PathVariable Long id) throws NotFoundException {
		return ResponseEntity.ok(userService.getUserById(id));
	}
	// @Operation(summary = "delete user", responses = { @ApiResponse(responseCode = "204"),
	// 		@ApiResponse(responseCode = "404", content = @Content) })
	@DeleteMapping("/{id}")
	@ResponseStatus(HttpStatus.NO_CONTENT)
	@PreAuthorize("hasAuthority('MAINTAIN_USER')")
	public void delete(@PathVariable long id) {
		userService.markDelete(userService.find(id).orElseThrow(NotFoundException::new));
	}

	// @Operation(summary = "new user")
	@PostMapping("/save")
//	@ResponseStatus(HttpStatus.CREATED)
//	@PreAuthorize("hasAuthority('MAINTAIN_USER')")
	public IdRes newRecord(@RequestBody @Valid NewUserReq req) throws UnsupportedEncodingException {
		return new IdRes(userService.newRecord(req).getId());
	}

	// @Operation(summary = "new user by public user")
	@PostMapping("/registry")
	@ResponseStatus(HttpStatus.CREATED)
	// @PreAuthorize("hasAuthority('MAINTAIN_USER')")
	public ResponseEntity<?> createPublicUserRecord(@RequestBody NewPublicUserReq req) throws UnsupportedEncodingException {
		logger.info("Create user request:");
		req.setPassword("mms1234"); // default for now
		return ResponseEntity.ok(new IdRes(userService.newPublicUserRecord(req).getId())) ;
	}

	// @Operation(summary = "update user", responses = {
	// 		@ApiResponse(responseCode = "204"),
	// 		@ApiResponse(responseCode = "400", content = @Content),
	// 		@ApiResponse(responseCode = "404", content = @Content),
	// })

	@PutMapping("/{id}")
	@ResponseStatus(HttpStatus.NO_CONTENT)
	@PreAuthorize("hasAuthority('MAINTAIN_USER')")
	public void updateRecord(@PathVariable int id, @RequestBody @Valid UpdateUserReq req) {
		userService.updateRecord(id, req);
	}

//	@PostMapping("/{id}")
//	@ResponseStatus(HttpStatus.NO_CONTENT)
//	@PreAuthorize("hasAuthority('MAINTAIN_USER')")
//	public void saveRecord(@RequestBody @Valid NewUserReq req) {
//		userService.newUser(req);
//	}

	// @Operation(summary = "current user change password", description = "error: USER_WRONG_NEW_PWD = new password not available", responses = {
	// 		@ApiResponse(responseCode = "204"),
	// 		@ApiResponse(responseCode = "400", content = @Content),
	// 		@ApiResponse(responseCode = "404", content = @Content),
	// 		@ApiResponse(responseCode = "422", content = @Content(schema = @Schema(implementation = FailureRes.class))),
	// })
	@PatchMapping("/change-password")
	@ResponseStatus(HttpStatus.NO_CONTENT)
	// @PreAuthorize("hasAuthority('MAINTAIN_USER')")
	public void changePassword(@RequestBody @Valid ChangePwdReq req) {
		long id = SecurityUtils.getUser().get().getId();
		User instance = userService.find(id).orElseThrow(NotFoundException::new);

		logger.info("TEST req: "+req.getPassword());
		logger.info("TEST instance: "+instance.getPassword());
		if (!passwordEncoder.matches(req.getPassword(), instance.getPassword())) {
			throw new BadRequestException();
		}

		PasswordRule rule = new PasswordRule(settingsService);
		if (!PasswordUtils.checkPwd(req.getNewPassword(), rule)) {
			throw new UnprocessableEntityException(ErrorCodes.USER_WRONG_NEW_PWD);
		}

		instance.setPassword(passwordEncoder.encode(req.getNewPassword()));
		userService.save(instance);
	}

	@PatchMapping("/admin-change-password")
	@ResponseStatus(HttpStatus.NO_CONTENT)
	@PreAuthorize("hasAuthority('MAINTAIN_USER')")
	public void adminChangePassword(@RequestBody @Valid AdminChangePwdReq req) {
		long id = req.getId();
		User instance = userService.find(id).orElseThrow(NotFoundException::new);

		instance.setPassword(passwordEncoder.encode(req.getNewPassword()));
		userService.save(instance);
	}

	// @Operation(summary = "reset password", responses = {
	// 		@ApiResponse(responseCode = "204"),
	// 		@ApiResponse(responseCode = "404", content = @Content),
	// })
	@PostMapping("/{id}/reset-password")
	@PreAuthorize("hasAuthority('MAINTAIN_USER')")
	@ResponseStatus(HttpStatus.NO_CONTENT)
	public ResponseEntity<?> resetPassword(@PathVariable long id) throws UnsupportedEncodingException {
		String password = userService.resetPassword(id);
		return ResponseEntity.ok(password);
	}


	// @Operation(summary = "get password rules")
	@GetMapping("/password-rule")
	public PasswordRule passwordRlue() {
		return new PasswordRule(settingsService);
	}

	@PostMapping("/export-qrcode")
  	public void exportQrCode(@Valid @RequestBody ExportUserQrCodeRequest request, HttpServletResponse response) 
        	throws Exception {
    	response.setCharacterEncoding("utf-8");
    	response.setContentType("application/pdf");
    	OutputStream out = response.getOutputStream();
    	Map<String, Object> pdf = userQrCodeService.exportUserQrCode(request);
    	JasperPrint jasperPrint = (JasperPrint) pdf.get("report");
    	String fileName = (String) pdf.get("fileName");
    	response.addHeader("Content-Disposition", "attachment; filename=\"" + fileName + "_qrcode.pdf\"");
    	out.write(JasperExportManager.exportReportToPdf(jasperPrint));
    	out.flush();
	}

	@PostMapping("/print-qrcode")
	public void printQrCode(@Valid @RequestBody PrintUserQrCodeRequest request) {
		userQrCodeService.printUserQrCode(request);
	}

	public static class AdminChangePwdReq {
		private Long id;
		@NotBlank
		private String newPassword;

		public Long getId() { return id; }
		public Long setId(Long id) { return this.id = id; }
		public String getNewPassword() {
			return newPassword;
		}
		public void setNewPassword(String newPassword) {
			this.newPassword = newPassword;
		}
	}

	public static class ChangePwdReq {
		@NotBlank
		private String password;
		@NotBlank
		private String newPassword;

		public String getPassword() {
			return password;
		}

		public void setPassword(String password) {
			this.password = password;
		}

		public String getNewPassword() {
			return newPassword;
		}

		public void setNewPassword(String newPassword) {
			this.newPassword = newPassword;
		}

	}

}
