package com.spring.domain.schedule.service;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.stereotype.Service;

import com.spring.common.error.BizBaseException;
import com.spring.domain.schedule.dto.ScheduleJobResponse;
import com.spring.domain.schedule.error.ScheduleNotFoundException;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class FindScheduleJobService {

    private final Scheduler scheduler;

    public List<ScheduleJobResponse> getAllJobs(final String groupName, final String jobName) {
        try {
            return getFilteredJobKeys(groupName, jobName)
                    .map(this::createScheduleSafely)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
        } catch (SchedulerException e) {
            return Collections.emptyList();
        }
    }

    public ScheduleJobResponse getJobDetail(final String groupName, final String jobName) {
        try {
            JobKey jobKey = new JobKey(jobName, groupName);
            if (scheduler.checkExists(jobKey)) {
                return createSchedule(jobKey);
            }
            throw new ScheduleNotFoundException();
        } catch (SchedulerException e) {
            throw new BizBaseException();
        }
    }
    
    private Stream<JobKey> getFilteredJobKeys(final String groupName, final String jobName) throws SchedulerException {
        return scheduler.getJobGroupNames().stream()
                .filter(group -> isGroupMatched(group, groupName))
                .flatMap(this::getJobKeysForGroup)
                .filter(jobKey -> isJobMatched(jobKey, jobName));
    }
    
    private boolean isGroupMatched(final String group, final String groupName) {
        return groupName == null || groupName.isEmpty() || 
                group.toLowerCase().contains(groupName.toLowerCase());
    }
    
    private Stream<JobKey> getJobKeysForGroup(final String group) {
        try {
            return scheduler.getJobKeys(GroupMatcher.jobGroupEquals(group)).stream();
        } catch (SchedulerException e) {
            return Stream.empty();
        }
    }
    
    private boolean isJobMatched(final JobKey jobKey, final String jobName) {
        return jobName == null || jobName.isEmpty() || 
                jobKey.getName().toLowerCase().contains(jobName.toLowerCase());
    }
    
    private ScheduleJobResponse createScheduleSafely(final JobKey jobKey) {
        try {
            return createSchedule(jobKey);
        } catch (SchedulerException e) {
            return null;
        }
    }

    private ScheduleJobResponse createSchedule(final JobKey jobKey) throws SchedulerException {
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        Optional<Trigger> trigger = Optional.ofNullable(scheduler.getTriggersOfJob(jobKey).stream().findFirst().orElse(null));
        return new ScheduleJobResponse(
            jobKey.getName(),
            jobKey.getGroup(),
            jobDetail.getDescription(),
            getCronExpression(trigger),
            getTriggerState(trigger),
            jobDetail.getJobClass().getName(),
            jobDetail.getJobDataMap().toString(),
            trigger.map(t -> t.getClass().getSimpleName()).orElse(""),
            trigger.map(Trigger::getNextFireTime).map(this::convertToLocalDateTime).orElse(null),
            trigger.map(Trigger::getPreviousFireTime).map(this::convertToLocalDateTime).orElse(null)
        );
    }

    private String getTriggerState(final Optional<Trigger> trigger) {
        return trigger.map(t -> {
            try {
                Trigger.TriggerState state = scheduler.getTriggerState(t.getKey());
                return state.name();
            } catch (SchedulerException e) {
                return Trigger.TriggerState.ERROR.name();
            }
        }).orElse(Trigger.TriggerState.NONE.name());
    }
    
    private String getCronExpression(final Optional<Trigger> trigger) {
        return trigger.map(t -> {
            if (t instanceof CronTrigger) {
                return ((CronTrigger) t).getCronExpression();
            }
            return t.getScheduleBuilder().toString();
        }).orElse("N/A");
    }

    private LocalDateTime convertToLocalDateTime(final Date date) {
        if (date == null) return null;
        return date.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();
    }
    
}
