package com.erp.ai.service;

import com.erp.ai.dto.ScheduleRequest;
import com.erp.ai.exception.AiAgentException;
import com.erp.ai.mapper.AiAgentScheduleMapper;
import com.erp.ai.model.AiAgentSchedule;
import com.erp.ai.scheduler.MultiAgentExecutionJob;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

/**
 * AI 스케줄 관리 서비스 (Quartz JDBC).
 * vexplor aiSchedulerService.ts 1:1 포팅.
 * architecture §8.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AiSchedulerService {

    private static final String JOB_GROUP = "ai-agent-schedule";
    private static final String TRIGGER_GROUP = "ai-agent-trigger";

    private final AiAgentScheduleMapper scheduleMapper;
    private final Scheduler scheduler;

    @Qualifier("aiObjectMapper")
    private final ObjectMapper objectMapper;

    /**
     * 서버 시작 시 활성 스케줄 모두 Quartz에 등록.
     */
    @PostConstruct
    public void initializeSchedules() {
        try {
            List<AiAgentSchedule> schedules = scheduleMapper.listActive();
            for (AiAgentSchedule s : schedules) {
                try {
                    registerJob(s);
                } catch (Exception e) {
                    log.warn("스케줄 등록 실패: {} - {}", s.getName(), e.getMessage());
                }
            }
            log.info("AI 스케줄러 초기화: {}개 활성 스케줄", schedules.size());
        } catch (Exception e) {
            log.warn("AI 스케줄러 초기화 실패: {}", e.getMessage());
        }
    }

    public List<Map<String, Object>> list() {
        return scheduleMapper.listAll().stream().map(this::toResponseMap).toList();
    }

    public Map<String, Object> getById(long id) {
        AiAgentSchedule s = scheduleMapper.getById(id);
        if (s == null) return null;
        return toResponseMap(s);
    }

    /** 내부용 — model 원본 반환 (Quartz 등록 경로) */
    public AiAgentSchedule getEntityById(long id) {
        return scheduleMapper.getById(id);
    }

    private Map<String, Object> toResponseMap(AiAgentSchedule s) {
        Map<String, Object> row = new HashMap<>();
        row.put("id", s.getId());
        row.put("name", s.getName());
        row.put("group_id", s.getGroup_id());
        row.put("cron_expression", s.getCron_expression());
        row.put("timezone", s.getTimezone());
        row.put("input_message", s.getInput_message());
        row.put("notification", s.getNotification());
        row.put("is_active", s.getIs_active());
        row.put("last_run_at", s.getLast_run_at());
        row.put("run_count", s.getRun_count());
        row.put("company_code", s.getCompany_code());
        row.put("created_by", s.getCreated_by());
        row.put("created_at", s.getCreated_at());
        row.put("updated_at", s.getUpdated_at());
        row.put("group_name", s.getGroup_name());
        parseJsonField(row, "notification");
        return row;
    }

    private void parseJsonField(Map<String, Object> row, String key) {
        Object val = row.get(key);
        if (val instanceof String s && !s.isBlank()) {
            try {
                row.put(key, objectMapper.readValue(s, Object.class));
            } catch (Exception e) {
                log.warn("Failed to parse JSONB field '{}': {}", key, e.getMessage());
            }
        }
    }

    @Transactional
    public AiAgentSchedule create(ScheduleRequest req, String userId) {
        if (!CronExpression.isValidExpression(req.getCron_expression())) {
            throw new AiAgentException("유효하지 않은 cron 표현식입니다.");
        }
        AiAgentSchedule s = new AiAgentSchedule();
        s.setName(req.getName());
        s.setGroup_id(req.getGroup_id());
        s.setCron_expression(req.getCron_expression());
        s.setTimezone(req.getTimezone() != null ? req.getTimezone() : "Asia/Seoul");
        s.setInput_message(req.getInput_message());
        s.setNotification(toJson(req.getNotification()));
        s.setIs_active(req.getIs_active() != null ? req.getIs_active() : Boolean.TRUE);
        s.setCreated_by(userId);
        scheduleMapper.insert(s);
        AiAgentSchedule saved = scheduleMapper.getById(s.getId());

        if (Boolean.TRUE.equals(saved.getIs_active())) {
            try {
                registerJob(saved);
            } catch (Exception e) {
                log.warn("Quartz 등록 실패: {}", e.getMessage());
            }
        }
        log.info("AI 스케줄 생성: {} ({})", req.getName(), req.getCron_expression());
        return saved;
    }

    @Transactional
    public AiAgentSchedule update(long id, ScheduleRequest req) {
        AiAgentSchedule existing = scheduleMapper.getById(id);
        if (existing == null) return null;

        if (req.getCron_expression() != null
                && !CronExpression.isValidExpression(req.getCron_expression())) {
            throw new AiAgentException("유효하지 않은 cron 표현식입니다.");
        }

        AiAgentSchedule s = new AiAgentSchedule();
        s.setId(id);
        s.setName(req.getName());
        s.setCron_expression(req.getCron_expression());
        s.setTimezone(req.getTimezone());
        s.setInput_message(req.getInput_message());
        if (req.getNotification() != null) s.setNotification(toJson(req.getNotification()));
        s.setIs_active(req.getIs_active());
        scheduleMapper.update(s);

        AiAgentSchedule saved = scheduleMapper.getById(id);
        try {
            unregisterJob(id);
            if (Boolean.TRUE.equals(saved.getIs_active())) registerJob(saved);
        } catch (Exception e) {
            log.warn("Quartz 재등록 실패: {}", e.getMessage());
        }
        return saved;
    }

    @Transactional
    public void delete(long id) {
        try {
            unregisterJob(id);
        } catch (Exception e) {
            log.warn("Quartz 해제 실패: {}", e.getMessage());
        }
        scheduleMapper.delete(id);
    }

    private void registerJob(AiAgentSchedule schedule) throws SchedulerException {
        if (!Boolean.TRUE.equals(schedule.getIs_active())) return;
        JobKey jobKey = new JobKey("schedule-" + schedule.getId(), JOB_GROUP);
        TriggerKey triggerKey = new TriggerKey("trigger-" + schedule.getId(), TRIGGER_GROUP);

        JobDetail jobDetail = JobBuilder.newJob(MultiAgentExecutionJob.class)
                .withIdentity(jobKey)
                .usingJobData("scheduleId", schedule.getId())
                .storeDurably()
                .build();

        TimeZone tz = TimeZone.getTimeZone(schedule.getTimezone() != null
                ? schedule.getTimezone() : "Asia/Seoul");
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerKey)
                .forJob(jobKey)
                .withSchedule(CronScheduleBuilder.cronSchedule(schedule.getCron_expression())
                        .inTimeZone(tz))
                .build();

        if (scheduler.checkExists(jobKey)) scheduler.deleteJob(jobKey);
        scheduler.scheduleJob(jobDetail, trigger);
    }

    private void unregisterJob(long scheduleId) throws SchedulerException {
        JobKey jobKey = new JobKey("schedule-" + scheduleId, JOB_GROUP);
        if (scheduler.checkExists(jobKey)) {
            scheduler.deleteJob(jobKey);
        }
    }

    private String toJson(Object value) {
        if (value == null) return "{}";
        try {
            return objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            return "{}";
        }
    }
}
