package com.cloudhandson.vpdbackoffice.web;

import com.cloudhandson.vpdbackoffice.domain.masking.MaskingRuleCreateCommand;
import com.cloudhandson.vpdbackoffice.domain.masking.MaskingTemplate;
import com.cloudhandson.vpdbackoffice.domain.protectedobject.ProtectedColumn;
import com.cloudhandson.vpdbackoffice.domain.protectedobject.ProtectedObject;
import com.cloudhandson.vpdbackoffice.service.AppException;
import com.cloudhandson.vpdbackoffice.service.MaskingRuleService;
import com.cloudhandson.vpdbackoffice.service.ProtectedObjectService;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
public class MaskingRuleController {

  private final MaskingRuleService maskingRuleService;
  private final ProtectedObjectService protectedObjectService;

  public MaskingRuleController(
      MaskingRuleService maskingRuleService,
      ProtectedObjectService protectedObjectService
  ) {
    this.maskingRuleService = maskingRuleService;
    this.protectedObjectService = protectedObjectService;
  }

  @GetMapping("/masking-rules")
  public String maskingRules(Model model) {
    var objects = protectedObjectService.findEnabled();
    var managedObjectNames = maskingRuleService.managedObjectNames();
    var policyStatuses = maskingRuleService.findPolicyStatuses();
    var columnsByObject = protectedObjectService.findColumnsByObjectIds(
        objects.stream().map(object -> object.objectId()).toList()
    );
    var maskingTargetObjects = objects.stream()
        .filter(object -> managedObjectNames.contains(object.objectName()))
        .toList();
    model.addAttribute("rules", maskingRuleService.findAllRules());
    model.addAttribute("templates", Arrays.asList(MaskingTemplate.values()));
    model.addAttribute("columnRules", maskingRuleService.findColumnRules());
    model.addAttribute("policyStatuses", policyStatuses);
    model.addAttribute("policyStatusByObjectName", policyStatuses.stream().collect(Collectors.toMap(
        status -> status.objectName(),
        status -> status,
        (left, right) -> left
    )));
    model.addAttribute("objects", objects);
    model.addAttribute("maskingTargetObjects", maskingTargetObjects);
    model.addAttribute("sensitiveColumnsByObject", objects.stream().collect(Collectors.toMap(
        object -> object.objectId(),
        object -> columnsByObject.getOrDefault(object.objectId(), List.of()).stream()
            .filter(column -> column.sensitive())
            .toList()
    )));
    model.addAttribute("availableMaskingColumnsByObject", maskingTargetObjects.stream().collect(Collectors.toMap(
        object -> object.objectId(),
        object -> availableMaskingColumns(object, columnsByObject.getOrDefault(object.objectId(), List.of()))
    )));
    return "masking-rules";
  }

  @PostMapping("/masking-rules")
  public String createRule(
      @RequestParam String ruleCode,
      @RequestParam String ruleName,
      @RequestParam String templateCode,
      @RequestParam(required = false) String description,
      RedirectAttributes redirectAttributes
  ) {
    try {
      maskingRuleService.createRule(new MaskingRuleCreateCommand(ruleCode, ruleName, templateCode, description));
      redirectAttributes.addFlashAttribute("message", "컬럼 마스킹 규칙을 등록했습니다.");
    } catch (AppException | DataAccessException exception) {
      redirectAttributes.addFlashAttribute("errorMessage", safeMessage(exception));
    }
    return "redirect:/masking-rules";
  }

  @PostMapping("/masking-rules/active")
  public String setRuleActive(
      @RequestParam long ruleId,
      @RequestParam boolean active,
      RedirectAttributes redirectAttributes
  ) {
    try {
      var result = maskingRuleService.setRuleActive(ruleId, active);
      redirectAttributes.addFlashAttribute("message", (active ? "컬럼 마스킹 규칙을 활성화했습니다. " : "컬럼 마스킹 규칙을 비활성화했습니다. ")
          + result.summary());
    } catch (AppException | DataAccessException exception) {
      redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
    }
    return "redirect:/masking-rules";
  }

  @PostMapping("/masking-rules/columns")
  public String assignColumnRule(
      @RequestParam long columnId,
      @RequestParam long ruleId,
      RedirectAttributes redirectAttributes
  ) {
    try {
      var result = maskingRuleService.assignRuleToColumn(columnId, ruleId);
      redirectAttributes.addFlashAttribute("message", "컬럼에 컬럼 마스킹 규칙을 연결했습니다. " + result.summary());
    } catch (AppException | DataAccessException exception) {
      redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
    }
    return "redirect:/masking-rules";
  }

  @PostMapping("/masking-rules/target-columns")
  public String addTargetColumn(
      @RequestParam String target,
      RedirectAttributes redirectAttributes
  ) {
    try {
      String[] parts = target == null ? new String[0] : target.split(":", 2);
      if (parts.length != 2) {
        throw new AppException("추가할 보호 객체와 컬럼을 선택하세요.");
      }
      var result = maskingRuleService.addTargetColumn(Long.parseLong(parts[0]), parts[1]);
      redirectAttributes.addFlashAttribute("message",
          "마스킹 대상 컬럼으로 등록했습니다. 실제 적용하려면 아래에서 기본 규칙을 연결하세요. " + result.summary());
    } catch (NumberFormatException exception) {
      redirectAttributes.addFlashAttribute("errorMessage", "추가할 보호 객체와 컬럼을 선택하세요.");
    } catch (AppException | DataAccessException exception) {
      redirectAttributes.addFlashAttribute("errorMessage", safeMessage(exception));
    }
    return "redirect:/masking-rules";
  }

  @PostMapping("/masking-rules/columns/delete")
  public String removeColumnRule(@RequestParam long columnId, RedirectAttributes redirectAttributes) {
    try {
      var result = maskingRuleService.removeRuleFromColumn(columnId);
      redirectAttributes.addFlashAttribute("message", "컬럼 마스킹 규칙과 관련 사용자 예외를 해제했습니다. " + result.summary());
    } catch (AppException | DataAccessException exception) {
      redirectAttributes.addFlashAttribute("errorMessage", exception.getMessage());
    }
    return "redirect:/masking-rules";
  }

  @PostMapping("/masking-rules/synchronize")
  public String synchronizeDatabasePolicies(RedirectAttributes redirectAttributes) {
    try {
      redirectAttributes.addFlashAttribute("message", maskingRuleService.synchronizeDatabasePolicies().summary());
    } catch (AppException | DataAccessException exception) {
      redirectAttributes.addFlashAttribute("errorMessage", safeMessage(exception));
    }
    return "redirect:/masking-rules";
  }

  private String safeMessage(Exception exception) {
    return exception instanceof AppException ? exception.getMessage() : "컬럼 마스킹 규칙을 저장하지 못했습니다. 입력값과 DB 상태를 확인하세요.";
  }

  private List<String> availableMaskingColumns(
      ProtectedObject object,
      List<ProtectedColumn> protectedColumns
  ) {
    var registeredSensitiveColumns = new HashSet<String>();
    protectedColumns.stream()
        .filter(ProtectedColumn::sensitive)
        .map(ProtectedColumn::columnName)
        .forEach(registeredSensitiveColumns::add);
    return protectedObjectService.findDatabaseColumns(object.owner(), object.objectName()).stream()
        .filter(columnName -> !registeredSensitiveColumns.contains(columnName))
        .toList();
  }
}
