package com.magistr.app.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;

import java.time.LocalDate;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "schedule_rules")
public class ScheduleRule {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(optional = false)
    @JoinColumn(name = "subject_id", nullable = false)
    private Subject subject;

    @ManyToOne(optional = false)
    @JoinColumn(name = "semester_id", nullable = false)
    private Semester semester;

    @Column(nullable = false, length = 20)
    private String status = LifecycleEntity.STATUS_ACTIVE;

    @Column(name = "valid_from")
    private LocalDate validFrom;

    @Column(name = "valid_to")
    private LocalDate validTo;

    @Column(name = "lecture_academic_hours", nullable = false)
    private Integer lectureAcademicHours = 0;

    @Column(name = "laboratory_academic_hours", nullable = false)
    private Integer laboratoryAcademicHours = 0;

    @Column(name = "practice_academic_hours", nullable = false)
    private Integer practiceAcademicHours = 0;

    @Column(name = "lecture_start_week", nullable = false)
    private Integer lectureStartWeek = 1;

    @Column(name = "laboratory_start_week", nullable = false)
    private Integer laboratoryStartWeek = 1;

    @Column(name = "practice_start_week", nullable = false)
    private Integer practiceStartWeek = 1;

    @ManyToMany
    @JoinTable(
            name = "schedule_rule_groups",
            joinColumns = @JoinColumn(name = "schedule_rule_id"),
            inverseJoinColumns = @JoinColumn(name = "group_id")
    )
    private Set<StudentGroup> groups = new HashSet<>();

    @OneToMany(mappedBy = "scheduleRule", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<ScheduleRuleSlot> slots = new HashSet<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public Semester getSemester() {
        return semester;
    }

    public void setSemester(Semester semester) {
        this.semester = semester;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status == null || status.isBlank() ? LifecycleEntity.STATUS_ACTIVE : status;
    }

    public LocalDate getValidFrom() {
        return validFrom;
    }

    public void setValidFrom(LocalDate validFrom) {
        this.validFrom = validFrom;
    }

    public LocalDate getValidTo() {
        return validTo;
    }

    public void setValidTo(LocalDate validTo) {
        this.validTo = validTo;
    }

    public Integer getLectureAcademicHours() {
        return lectureAcademicHours;
    }

    public void setLectureAcademicHours(Integer lectureAcademicHours) {
        this.lectureAcademicHours = lectureAcademicHours;
    }

    public Integer getLaboratoryAcademicHours() {
        return laboratoryAcademicHours;
    }

    public void setLaboratoryAcademicHours(Integer laboratoryAcademicHours) {
        this.laboratoryAcademicHours = laboratoryAcademicHours;
    }

    public Integer getPracticeAcademicHours() {
        return practiceAcademicHours;
    }

    public void setPracticeAcademicHours(Integer practiceAcademicHours) {
        this.practiceAcademicHours = practiceAcademicHours;
    }

    public Integer getLectureStartWeek() {
        return lectureStartWeek;
    }

    public void setLectureStartWeek(Integer lectureStartWeek) {
        this.lectureStartWeek = lectureStartWeek;
    }

    public Integer getLaboratoryStartWeek() {
        return laboratoryStartWeek;
    }

    public void setLaboratoryStartWeek(Integer laboratoryStartWeek) {
        this.laboratoryStartWeek = laboratoryStartWeek;
    }

    public Integer getPracticeStartWeek() {
        return practiceStartWeek;
    }

    public void setPracticeStartWeek(Integer practiceStartWeek) {
        this.practiceStartWeek = practiceStartWeek;
    }

    public int academicHoursFor(ScheduleLessonCategory category) {
        return switch (category) {
            case LECTURE -> valueOrZero(lectureAcademicHours);
            case LABORATORY -> valueOrZero(laboratoryAcademicHours);
            case PRACTICE -> valueOrZero(practiceAcademicHours);
        };
    }

    public int startWeekFor(ScheduleLessonCategory category) {
        return switch (category) {
            case LECTURE -> valueOrDefault(lectureStartWeek, 1);
            case LABORATORY -> valueOrDefault(laboratoryStartWeek, 1);
            case PRACTICE -> valueOrDefault(practiceStartWeek, 1);
        };
    }

    @JsonIgnore
    public boolean isActiveRecord() {
        return !LifecycleEntity.STATUS_ARCHIVED.equals(status);
    }

    @JsonIgnore
    public boolean isArchivedRecord() {
        return LifecycleEntity.STATUS_ARCHIVED.equals(status);
    }

    @JsonIgnore
    public boolean isActiveOn(LocalDate date) {
        if (date == null) {
            return isActiveRecord();
        }
        if (!isActiveRecord()) {
            return false;
        }
        boolean afterStart = validFrom == null || !date.isBefore(validFrom);
        boolean beforeEnd = validTo == null || !date.isAfter(validTo);
        return afterStart && beforeEnd;
    }

    public void archive() {
        status = LifecycleEntity.STATUS_ARCHIVED;
    }

    public void restore() {
        status = LifecycleEntity.STATUS_ACTIVE;
        validTo = null;
    }

    private int valueOrZero(Integer value) {
        return valueOrDefault(value, 0);
    }

    private int valueOrDefault(Integer value, int defaultValue) {
        return value == null ? defaultValue : value;
    }

    public Set<StudentGroup> getGroups() {
        return groups;
    }

    public void setGroups(Set<StudentGroup> groups) {
        this.groups = groups;
    }

    public Set<ScheduleRuleSlot> getSlots() {
        return slots;
    }

    public void setSlots(Set<ScheduleRuleSlot> slots) {
        this.slots = slots;
    }
}
