package com.mzh.library.service;

import com.mzh.library.dao.SystemLogDao;
import com.mzh.library.entity.AuthenticatedUser;
import com.mzh.library.entity.Permission;
import com.mzh.library.entity.Role;
import com.mzh.library.entity.SystemLog;
import com.mzh.library.entity.SystemLogPage;
import com.mzh.library.entity.SystemLogSearchCriteria;
import com.mzh.library.exception.DaoException;
import com.mzh.library.service.impl.SystemLogServiceImpl;

import java.sql.Connection;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public final class SystemLogServiceCheck {
    private static final String DENIED_MESSAGE = "您无权查看系统日志。";
    private static final String UNAVAILABLE_MESSAGE =
        "系统日志服务暂时不可用，请稍后重试。";

    private SystemLogServiceCheck() {
    }

    public static void main(String[] args) {
        Logger.getLogger(SystemLogServiceImpl.class.getName()).setLevel(Level.OFF);

        InMemorySystemLogDao dao = new InMemorySystemLogDao();
        dao.logs.add(log(1L, "user.create", "Created account username=staff"));
        dao.logs.add(log(2L, "user.update", "Updated account username=staff"));
        SystemLogService service = new SystemLogServiceImpl(dao);

        AuthenticatedUser admin = actor(1L, Role.ADMINISTRATOR);
        AuthenticatedUser reader = actor(2L, Role.READER);

        requireMessage(service.searchLogs(reader, new SystemLogSearchCriteria()), DENIED_MESSAGE);

        ServiceResult<SystemLogPage> pageResult = service.searchLogs(admin, new SystemLogSearchCriteria());
        require(pageResult.isSuccessful(), "administrator should view system logs");
        require(pageResult.getData().getLogs().size() == 2, "system log page should include log entries");
        require(pageResult.getData().getOperationTypes().contains("user.create"),
            "system log page should include operation type filters");

        ServiceResult<SystemLogPage> invalidDate = service.searchLogs(admin,
            new SystemLogSearchCriteria("", "", "bad-date", ""));
        require(!invalidDate.isSuccessful(), "invalid date should fail validation");
        require(invalidDate.getErrors().containsKey("createdFrom"), "invalid start date should target createdFrom");

        ServiceResult<SystemLogPage> invalidRange = service.searchLogs(admin,
            new SystemLogSearchCriteria("", "", "2026-04-30", "2026-04-01"));
        require(!invalidRange.isSuccessful(), "inverted date range should fail validation");
        require(invalidRange.getErrors().containsKey("createdTo"), "inverted range should target createdTo");

        SystemLog orphanedOperator = log(99L, "user.update", "Updated orphaned operator account");
        orphanedOperator.setOperatorUsername("");
        orphanedOperator.setOperatorDisplayName("");
        require("用户 #1".equals(orphanedOperator.getOperatorLabel()),
            "operator id should still render when joined user names are unavailable");
        require("管理员".equals(orphanedOperator.getOperatorMetaText()),
            "operator meta should display role when names are unavailable");

        SystemLog unsafeStatus = log(100L, "user.update", "Unsafe status check");
        unsafeStatus.setResultStatus("success\" onclick=\"x");
        require("unknown".equals(unsafeStatus.getResultStatusCode()),
            "unexpected log result status should not become a raw CSS class");
        require("success\" onclick=\"x".equals(unsafeStatus.getResultStatusName()),
            "unexpected log result status should remain escaped display text");

        SystemLogService failingService = new SystemLogServiceImpl(new FailingSystemLogDao());
        requireMessage(failingService.searchLogs(admin, new SystemLogSearchCriteria()), UNAVAILABLE_MESSAGE);
    }

    private static SystemLog log(long id, String operationType, String message) {
        SystemLog log = new SystemLog();
        log.setId(id);
        log.setOperatorId(1L);
        log.setOperatorRole(Role.ADMINISTRATOR.getCode());
        log.setOperatorUsername("admin");
        log.setOperatorDisplayName("System Administrator");
        log.setOperationType(operationType);
        log.setTargetTable("users");
        log.setTargetId(String.valueOf(id));
        log.setResultStatus("success");
        log.setMessage(message);
        log.setRequestIp("127.0.0.1");
        log.setCreatedAt(LocalDateTime.of(2026, 4, 27, 12, 0));
        return log;
    }

    private static AuthenticatedUser actor(long id, Role role) {
        return new AuthenticatedUser(id, role.getCode(), role.getDisplayName(), role,
            role == Role.ADMINISTRATOR
                ? EnumSet.allOf(Permission.class)
                : EnumSet.of(Permission.VIEW_CATALOG, Permission.BORROW_BOOKS));
    }

    private static void requireMessage(ServiceResult<?> result, String message) {
        require(!result.isSuccessful(), "result should be a failure");
        require(message.equals(result.getMessage()), "expected message: " + message);
    }

    private static void require(boolean condition, String message) {
        if (!condition) {
            throw new AssertionError(message);
        }
    }

    private static final class InMemorySystemLogDao implements SystemLogDao {
        private final List<SystemLog> logs = new ArrayList<>();

        @Override
        public List<SystemLog> search(SystemLogSearchCriteria criteria) {
            return new ArrayList<>(logs);
        }

        @Override
        public List<String> findOperationTypes() {
            return Arrays.asList("user.create", "user.update");
        }

        @Override
        public long create(Connection connection, SystemLog log) {
            logs.add(log);
            return logs.size();
        }
    }

    private static final class FailingSystemLogDao implements SystemLogDao {
        @Override
        public List<SystemLog> search(SystemLogSearchCriteria criteria) {
            throw new DaoException("Simulated system log search failure", null);
        }

        @Override
        public List<String> findOperationTypes() {
            throw new DaoException("Simulated operation type failure", null);
        }

        @Override
        public long create(Connection connection, SystemLog log) {
            throw new DaoException("Simulated system log create failure", null);
        }
    }
}
