import json
import os
from datetime import date, datetime, timedelta
from functools import wraps

from flask import Flask, flash, redirect, render_template, request, session, url_for
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import inspect, select, text


BASE_DIR = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)
app.config.update(
    SECRET_KEY=os.environ.get("SECRET_KEY", "gelistirmede-degistirilecek-guclu-anahtar"),
    SQLALCHEMY_DATABASE_URI=f"sqlite:///{os.path.join(BASE_DIR, 'study_manager.sqlite3')}",
    SQLALCHEMY_TRACK_MODIFICATIONS=False,
    SESSION_TYPE="filesystem",
    SESSION_FILE_DIR=os.environ.get(
        "SESSION_FILE_DIR", os.path.join(BASE_DIR, "instance", "flask_session")
    ),
    SESSION_PERMANENT=False,
)
os.makedirs(app.config["SESSION_FILE_DIR"], exist_ok=True)
db = SQLAlchemy(app)
Session(app)


class Book(db.Model):
    __tablename__ = "books"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(180), nullable=False)
    category = db.Column(db.String(100), nullable=False)
    grade = db.Column(db.String(60), nullable=False)
    exam_type = db.Column(db.String(10), nullable=False, default="Yok")
    created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
    tasks = db.relationship("Task", backref="book", cascade="all, delete-orphan", lazy=True)
    progress_logs = db.relationship(
        "ProgressLog", backref="book", cascade="all, delete-orphan", lazy=True
    )


class Task(db.Model):
    __tablename__ = "tasks"

    id = db.Column(db.Integer, primary_key=True)
    book_id = db.Column(db.Integer, db.ForeignKey("books.id"), nullable=False, index=True)
    task_type = db.Column(db.String(10), nullable=False)
    start_page = db.Column(db.Integer, nullable=False)
    end_page = db.Column(db.Integer, nullable=False)
    priority = db.Column(db.Integer, nullable=False, default=3)
    description = db.Column(db.String(250), nullable=True)
    control_date = db.Column(db.Date, nullable=True, index=True)
    is_completed = db.Column(db.Boolean, nullable=False, default=False, index=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)

    @property
    def page_count(self):
        return self.end_page - self.start_page + 1


class ProgressLog(db.Model):
    __tablename__ = "progress_logs"

    id = db.Column(db.Integer, primary_key=True)
    book_id = db.Column(db.Integer, db.ForeignKey("books.id"), nullable=False, index=True)
    start_page = db.Column(db.Integer, nullable=False)
    end_page = db.Column(db.Integer, nullable=False)
    completed_on = db.Column(db.Date, nullable=False, default=date.today, index=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)


class Schedule(db.Model):
    __tablename__ = "schedules"

    id = db.Column(db.Integer, primary_key=True)
    start_date = db.Column(db.Date, nullable=False)
    end_date = db.Column(db.Date, nullable=False)
    json_data = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)


def login_required(view):
    @wraps(view)
    def wrapped_view(*args, **kwargs):
        if not session.get("logged_in"):
            flash("Bu sayfayı görüntülemek için giriş yapmalısınız.", "warning")
            return redirect(url_for("login"))
        return view(*args, **kwargs)

    return wrapped_view


def parse_int(value, label, minimum=None, maximum=None):
    try:
        parsed = int(value)
    except (TypeError, ValueError):
        raise ValueError(f"{label} geçerli bir sayı olmalıdır.")
    if minimum is not None and parsed < minimum:
        raise ValueError(f"{label} en az {minimum} olmalıdır.")
    if maximum is not None and parsed > maximum:
        raise ValueError(f"{label} en fazla {maximum} olabilir.")
    return parsed


def parse_iso_date(value, label):
    try:
        return datetime.strptime(value, "%Y-%m-%d").date()
    except (TypeError, ValueError):
        raise ValueError(f"{label} geçerli bir tarih olmalıdır.")


def get_page_range(form):
    start_page = parse_int(form.get("start_page"), "Başlangıç sayfası", 1)
    end_page = parse_int(form.get("end_page"), "Bitiş sayfası", 1)
    if start_page > end_page:
        raise ValueError("Başlangıç sayfası bitiş sayfasından büyük olamaz.")
    return start_page, end_page


def get_control_date(form, task_type):
    if task_type != "Ödev":
        return None
    control_date = parse_iso_date(form.get("control_date"), "Kontrol tarihi")
    if control_date <= date.today():
        raise ValueError("Ödevin kontrol tarihi bugünden sonra olmalıdır.")
    return control_date


def get_optional_description(form):
    description = form.get("description", "").strip()
    if len(description) > 250:
        raise ValueError("Açıklama en fazla 250 karakter olabilir.")
    return description or None


def subtract_interval(task, remove_start, remove_end):
    if remove_end < task.start_page or remove_start > task.end_page:
        return [(task.start_page, task.end_page)]
    remaining = []
    if task.start_page < remove_start:
        remaining.append((task.start_page, remove_start - 1))
    if remove_end < task.end_page:
        remaining.append((remove_end + 1, task.end_page))
    return remaining


def replace_task_with_ranges(task, ranges):
    """Replace one task with its remaining page intervals."""
    db.session.delete(task)
    for start_page, end_page in ranges:
        if start_page <= end_page:
            db.session.add(
                Task(
                    book_id=task.book_id,
                    task_type=task.task_type,
                    start_page=start_page,
                    end_page=end_page,
                    priority=task.priority,
                    description=task.description,
                    control_date=task.control_date,
                    is_completed=False,
                )
            )


def remove_range_from_tasks(tasks, start_page, end_page):
    affected = 0
    for task in tasks:
        if task.end_page < start_page or task.start_page > end_page:
            continue
        affected += 1
        replace_task_with_ranges(task, subtract_interval(task, start_page, end_page))
    return affected


def apply_assignment_precedence(book_id, start_page, end_page, excluded_task_id=None):
    statement = select(Task).where(
        Task.book_id == book_id,
        Task.task_type == "Görev",
        Task.is_completed.is_(False),
        Task.end_page >= start_page,
        Task.start_page <= end_page,
    )
    candidates = list(db.session.scalars(statement))
    if excluded_task_id is not None:
        candidates = [task for task in candidates if task.id != excluded_task_id]
    return remove_range_from_tasks(candidates, start_page, end_page)


def active_tasks():
    return list(
        db.session.scalars(
            select(Task).where(Task.is_completed.is_(False)).order_by(Task.priority.desc(), Task.created_at)
        )
    )


def serialize_builder_task(task):
    return {
        "id": task.id,
        "book_id": task.book_id,
        "book_name": task.book.name,
        "task_type": task.task_type,
        "description": task.description or "",
        "start_page": task.start_page,
        "end_page": task.end_page,
        "priority": task.priority,
        "control_date": task.control_date.isoformat() if task.control_date else None,
    }


def builder_tasks():
    """Order assignments by nearest control date, then regular tasks by priority."""
    open_tasks = active_tasks()
    assignments = [task for task in open_tasks if task.task_type == "Ödev"]
    regular_tasks = [task for task in open_tasks if task.task_type != "Ödev"]
    assignments.sort(key=lambda task: (task.control_date is None, task.control_date or date.max, task.created_at))
    regular_tasks.sort(key=lambda task: (-task.priority, task.created_at))
    return [serialize_builder_task(task) for task in assignments + regular_tasks]


def assignment_alerts(end_date):
    return list(
        db.session.scalars(
            select(Task)
            .where(
                Task.task_type == "Ödev",
                Task.is_completed.is_(False),
                Task.control_date.is_not(None),
                Task.control_date <= end_date,
            )
            .order_by(Task.control_date, Task.created_at)
        )
    )


def empty_builder_state(start_date, end_date):
    days = []
    current = start_date
    while current <= end_date:
        days.append({"date": current.isoformat(), "slots": []})
        current += timedelta(days=1)
    return {
        "version": 3,
        "start_date": start_date.isoformat(),
        "end_date": end_date.isoformat(),
        "days": days,
    }


def parse_time(value, label):
    try:
        return datetime.strptime(str(value), "%H:%M").strftime("%H:%M")
    except (TypeError, ValueError):
        raise ValueError(f"{label} SS:DD biçiminde geçerli bir saat olmalıdır.")


def validate_builder_schedule(raw_json):
    """Validate browser state and write a canonical, historical schedule snapshot."""
    try:
        payload = json.loads(raw_json)
    except (TypeError, json.JSONDecodeError):
        raise ValueError("Program verisi okunamadı. Sayfayı yenileyip tekrar deneyin.")

    version = payload.get("version") if isinstance(payload, dict) else None
    if version not in {2, 3}:
        raise ValueError("Program verisi geçerli bir builder kaydı değil.")

    start_date = parse_iso_date(payload.get("start_date"), "Başlangıç tarihi")
    end_date = parse_iso_date(payload.get("end_date"), "Bitiş tarihi")
    if start_date > end_date:
        raise ValueError("Başlangıç tarihi bitiş tarihinden sonra olamaz.")

    raw_days = payload.get("days")
    if not isinstance(raw_days, list):
        raise ValueError("Program günleri geçersiz.")

    expected_dates = []
    current = start_date
    while current <= end_date:
        expected_dates.append(current.isoformat())
        current += timedelta(days=1)
    if len(raw_days) != len(expected_dates):
        raise ValueError("Program, seçilen tarih aralığındaki tüm günleri içermelidir.")

    allocations = {}
    canonical_days = []
    for index, raw_day in enumerate(raw_days):
        if not isinstance(raw_day, dict) or raw_day.get("date") != expected_dates[index]:
            raise ValueError("Program günleri seçilen tarih aralığıyla eşleşmiyor.")
        raw_slots = raw_day.get("slots")
        if not isinstance(raw_slots, list):
            raise ValueError("Bir günün saat hücreleri geçersiz.")

        canonical_slots = []
        for raw_slot in raw_slots:
            if not isinstance(raw_slot, dict):
                raise ValueError("Saat hücresi geçersiz.")
            time_start = parse_time(raw_slot.get("time_start"), "Başlangıç saati")
            time_end = parse_time(raw_slot.get("time_end"), "Bitiş saati")
            if time_start >= time_end:
                raise ValueError("Saat aralığının bitişi başlangıcından sonra olmalıdır.")

            if version == 2:
                raw_contents = [] if raw_slot.get("task_id") in (None, "") else [raw_slot]
            else:
                raw_contents = raw_slot.get("contents", [])
            if not isinstance(raw_contents, list):
                raise ValueError("Saat dilimindeki içerikler geçersiz.")

            canonical_contents = []
            for raw_content in raw_contents:
                if not isinstance(raw_content, dict):
                    raise ValueError("Saat dilimi içeriği geçersiz.")
                task_id = parse_int(raw_content.get("task_id"), "Görev")
                task = db.session.scalar(
                    select(Task).where(Task.id == task_id, Task.is_completed.is_(False))
                )
                if task is None:
                    raise ValueError("Seçtiğiniz görev artık aktif değil. Programı güncelleyin.")

                page_start = parse_int(raw_content.get("page_start"), "Başlangıç sayfası", 1)
                page_end = parse_int(raw_content.get("page_end"), "Bitiş sayfası", 1)
                if page_start > page_end:
                    raise ValueError("Başlangıç sayfası bitiş sayfasından büyük olamaz.")
                if page_start < task.start_page or page_end > task.end_page:
                    raise ValueError(
                        f"{task.book.name} için seçilen sayfalar görev aralığının dışında."
                    )

                allocations.setdefault(task.id, []).append((page_start, page_end))
                canonical_contents.append(
                    {
                        "task_id": task.id,
                        "book_id": task.book_id,
                        "book_name": task.book.name,
                        "task_type": task.task_type,
                        "description": task.description or "",
                        "page_start": page_start,
                        "page_end": page_end,
                    }
                )
            canonical_slots.append(
                {
                    "time_start": time_start,
                    "time_end": time_end,
                    "contents": canonical_contents,
                }
            )
        canonical_days.append({"date": expected_dates[index], "slots": canonical_slots})

    for task_id, intervals in allocations.items():
        intervals.sort()
        previous_end = None
        for start_page, end_page in intervals:
            if previous_end is not None and start_page <= previous_end:
                task = db.session.get(Task, task_id)
                raise ValueError(f"{task.book.name} için aynı sayfa iki kez planlanamaz.")
            previous_end = end_page

    return {
        "version": 3,
        "start_date": start_date.isoformat(),
        "end_date": end_date.isoformat(),
        "days": canonical_days,
    }, start_date, end_date


def normalize_slot_for_view(raw_slot):
    """Convert a single-content v2 slot to the current multi-content shape."""
    if not isinstance(raw_slot, dict):
        return {"time_start": "", "time_end": "", "contents": []}
    contents = raw_slot.get("contents")
    if not isinstance(contents, list):
        contents = []
        if raw_slot.get("task_id") or raw_slot.get("book_name"):
            contents.append(
                {
                    "task_id": raw_slot.get("task_id"),
                    "book_id": raw_slot.get("book_id"),
                    "book_name": raw_slot.get("book_name", ""),
                    "task_type": raw_slot.get("task_type", ""),
                    "description": raw_slot.get("description", ""),
                    "page_start": raw_slot.get("page_start"),
                    "page_end": raw_slot.get("page_end"),
                    "pages_label": raw_slot.get("pages_label", ""),
                }
            )
    return {
        "time_start": raw_slot.get("time_start", ""),
        "time_end": raw_slot.get("time_end", ""),
        "contents": contents,
    }


def normalize_schedule_for_view(schedule):
    """Keep v2 and older flat-list schedule records readable after the v3 upgrade."""
    try:
        raw_data = json.loads(schedule.json_data)
    except json.JSONDecodeError:
        raise ValueError("Bu programın kaydedilmiş verisi okunamıyor.")

    if isinstance(raw_data, dict) and raw_data.get("version") in {2, 3}:
        raw_days = raw_data.get("days", [])
        if not isinstance(raw_days, list):
            raise ValueError("Bu programın günleri okunamıyor.")
        return {
            "version": 3,
            "start_date": raw_data.get("start_date", schedule.start_date.isoformat()),
            "end_date": raw_data.get("end_date", schedule.end_date.isoformat()),
            "days": [
                {
                    "date": day.get("date", ""),
                    "slots": [normalize_slot_for_view(slot) for slot in day.get("slots", [])],
                }
                for day in raw_days
                if isinstance(day, dict) and isinstance(day.get("slots", []), list)
            ],
        }
    if not isinstance(raw_data, list):
        raise ValueError("Bu programın biçimi desteklenmiyor.")

    grouped = {}
    for item in raw_data:
        if not isinstance(item, dict) or not item.get("Date"):
            continue
        grouped.setdefault(item["Date"], []).append(
            {
                "time_start": str(item.get("Time_Slot", "")).split("-")[0].strip(),
                "time_end": "-".join(str(item.get("Time_Slot", "")).split("-")[1:]).strip(),
                "contents": [
                    {
                        "task_id": None,
                        "book_id": None,
                        "book_name": item.get("Book_Name", ""),
                        "task_type": item.get("Task_Type", ""),
                        "description": "",
                        "page_start": None,
                        "page_end": None,
                        "pages_label": item.get("Pages_to_Read", ""),
                    }
                ],
            }
        )
    return {
        "version": 3,
        "start_date": schedule.start_date.isoformat(),
        "end_date": schedule.end_date.isoformat(),
        "days": [{"date": day, "slots": slots} for day, slots in sorted(grouped.items())],
    }


def render_schedule_builder(start_date=None, end_date=None, state=None):
    builder_mode = start_date is not None and end_date is not None
    if builder_mode and state is None:
        state = empty_builder_state(start_date, end_date)
    return render_template(
        "schedule_create.html",
        builder_mode=builder_mode,
        start_date=start_date,
        end_date=end_date,
        builder_state=state,
        builder_tasks=builder_tasks() if builder_mode else [],
        due_assignments=assignment_alerts(end_date) if builder_mode else [],
        today_iso=date.today().isoformat(),
    )


@app.route("/giris", methods=["GET", "POST"])
def login():
    if session.get("logged_in"):
        return redirect(url_for("dashboard"))
    if request.method == "POST":
        username = request.form.get("username", "")
        password = request.form.get("password", "")
        if username == "mervan" and password == "Mmty2676*":
            session.clear()
            session["logged_in"] = True
            session["username"] = username
            flash("Tekrar hoş geldin, Mervan.", "success")
            return redirect(url_for("dashboard"))
        flash("Kullanıcı adı veya parola hatalı.", "error")
    return render_template("login.html")


@app.route("/cikis")
def logout():
    session.clear()
    flash("Güvenle çıkış yaptınız.", "success")
    return redirect(url_for("login"))


@app.route("/")
@login_required
def dashboard():
    tasks = active_tasks()
    total_pages = sum(task.page_count for task in tasks)
    high_priority = sum(1 for task in tasks if task.priority >= 4)
    recent_schedules = list(
        db.session.scalars(select(Schedule).order_by(Schedule.created_at.desc()).limit(3))
    )
    return render_template(
        "dashboard.html",
        active_task_count=len(tasks),
        total_pages=total_pages,
        high_priority=high_priority,
        book_count=db.session.scalar(select(db.func.count(Book.id))) or 0,
        tasks=tasks[:6],
        schedules=recent_schedules,
    )


@app.route("/kitaplar", methods=["GET", "POST"])
@login_required
def books():
    if request.method == "POST":
        name = request.form.get("name", "").strip()
        category = request.form.get("category", "").strip()
        grade = request.form.get("grade", "").strip()
        exam_type = request.form.get("exam_type", "Yok")
        if not name or not category or not grade:
            flash("Kitap adı, kategori ve sınıf alanları zorunludur.", "error")
        elif exam_type not in {"TYT", "AYT", "Yok"}:
            flash("Geçersiz sınav türü seçildi.", "error")
        else:
            db.session.add(Book(name=name, category=category, grade=grade, exam_type=exam_type))
            db.session.commit()
            flash("Kitap başarıyla eklendi.", "success")
            return redirect(url_for("books"))
    return render_template("books.html", books=list(db.session.scalars(select(Book).order_by(Book.name))))


@app.route("/kitaplar/<int:book_id>/duzenle", methods=["GET", "POST"])
@login_required
def edit_book(book_id):
    book = db.get_or_404(Book, book_id)
    if request.method == "POST":
        name = request.form.get("name", "").strip()
        category = request.form.get("category", "").strip()
        grade = request.form.get("grade", "").strip()
        exam_type = request.form.get("exam_type", "Yok")
        if not name or not category or not grade or exam_type not in {"TYT", "AYT", "Yok"}:
            flash("Lütfen tüm kitap bilgilerini geçerli biçimde doldurun.", "error")
        else:
            book.name, book.category, book.grade, book.exam_type = name, category, grade, exam_type
            db.session.commit()
            flash("Kitap bilgileri güncellendi.", "success")
            return redirect(url_for("books"))
    return render_template("book_form.html", book=book)


@app.post("/kitaplar/<int:book_id>/sil")
@login_required
def delete_book(book_id):
    db.session.delete(db.get_or_404(Book, book_id))
    db.session.commit()
    flash("Kitap ve buna bağlı görevler silindi.", "success")
    return redirect(url_for("books"))


@app.route("/gorevler", methods=["GET", "POST"])
@login_required
def tasks():
    books_list = list(db.session.scalars(select(Book).order_by(Book.name)))
    if request.method == "POST":
        try:
            book_id = parse_int(request.form.get("book_id"), "Kitap")
            book = db.get_or_404(Book, book_id)
            task_type = request.form.get("task_type")
            if task_type not in {"Görev", "Ödev"}:
                raise ValueError("Geçerli bir iş türü seçmelisiniz.")
            start_page, end_page = get_page_range(request.form)
            priority = parse_int(request.form.get("priority"), "Önem derecesi", 0, 5)
            description = get_optional_description(request.form)
            control_date = get_control_date(request.form, task_type)
            if task_type == "Ödev":
                apply_assignment_precedence(book.id, start_page, end_page)
            db.session.add(
                Task(
                    book_id=book.id,
                    task_type=task_type,
                    start_page=start_page,
                    end_page=end_page,
                    priority=priority,
                    description=description,
                    control_date=control_date,
                )
            )
            db.session.commit()
            flash("İş başarıyla kaydedildi. Ödev varsa çakışan görev sayfaları ayrıştırıldı.", "success")
            return redirect(url_for("tasks"))
        except ValueError as error:
            flash(str(error), "error")
    return render_template(
        "tasks.html",
        tasks=active_tasks(),
        books=books_list,
        minimum_control_date=(date.today() + timedelta(days=1)).isoformat(),
    )


@app.route("/gorevler/<int:task_id>/duzenle", methods=["GET", "POST"])
@login_required
def edit_task(task_id):
    task = db.get_or_404(Task, task_id)
    books_list = list(db.session.scalars(select(Book).order_by(Book.name)))
    if request.method == "POST":
        try:
            book_id = parse_int(request.form.get("book_id"), "Kitap")
            book = db.get_or_404(Book, book_id)
            task_type = request.form.get("task_type")
            if task_type not in {"Görev", "Ödev"}:
                raise ValueError("Geçerli bir iş türü seçmelisiniz.")
            start_page, end_page = get_page_range(request.form)
            priority = parse_int(request.form.get("priority"), "Önem derecesi", 0, 5)
            description = get_optional_description(request.form)
            control_date = get_control_date(request.form, task_type)
            task.book_id = book.id
            task.task_type = task_type
            task.start_page = start_page
            task.end_page = end_page
            task.priority = priority
            task.description = description
            task.control_date = control_date
            if task_type == "Ödev":
                apply_assignment_precedence(book.id, start_page, end_page, excluded_task_id=task.id)
            db.session.commit()
            flash("İş güncellendi.", "success")
            return redirect(url_for("tasks"))
        except ValueError as error:
            flash(str(error), "error")
    return render_template(
        "task_form.html",
        task=task,
        books=books_list,
        minimum_control_date=(date.today() + timedelta(days=1)).isoformat(),
    )


@app.post("/gorevler/<int:task_id>/tamamla")
@login_required
def complete_task(task_id):
    task = db.get_or_404(Task, task_id)
    db.session.add(
        ProgressLog(
            book_id=task.book_id,
            start_page=task.start_page,
            end_page=task.end_page,
            completed_on=date.today(),
        )
    )
    task.is_completed = True
    db.session.commit()
    flash("İş tamamlandı olarak işaretlendi ve ilerleme günlüğüne eklendi.", "success")
    return redirect(url_for("tasks"))


@app.post("/gorevler/<int:task_id>/sil")
@login_required
def delete_task(task_id):
    db.session.delete(db.get_or_404(Task, task_id))
    db.session.commit()
    flash("İş silindi.", "success")
    return redirect(url_for("tasks"))


@app.route("/ilerleme", methods=["GET", "POST"])
@login_required
def progress():
    books_list = list(db.session.scalars(select(Book).order_by(Book.name)))
    if request.method == "POST":
        try:
            book_id = parse_int(request.form.get("book_id"), "Kitap")
            book = db.get_or_404(Book, book_id)
            start_page, end_page = get_page_range(request.form)
            completed_on = parse_iso_date(request.form.get("completed_on"), "Tamamlanma tarihi")
            candidates = list(
                db.session.scalars(
                    select(Task).where(
                        Task.book_id == book.id,
                        Task.is_completed.is_(False),
                        Task.end_page >= start_page,
                        Task.start_page <= end_page,
                    )
                )
            )
            affected = remove_range_from_tasks(candidates, start_page, end_page)
            db.session.add(
                ProgressLog(
                    book_id=book.id,
                    start_page=start_page,
                    end_page=end_page,
                    completed_on=completed_on,
                )
            )
            db.session.commit()
            if affected:
                flash("İlerleme kaydedildi; ilgili aktif sayfa aralıkları güncellendi.", "success")
            else:
                flash("İlerleme kaydedildi. Bu aralıkta aktif iş bulunmuyordu.", "warning")
            return redirect(url_for("progress"))
        except ValueError as error:
            flash(str(error), "error")
    logs = list(
        db.session.scalars(
            select(ProgressLog)
            .order_by(ProgressLog.completed_on.desc(), ProgressLog.created_at.desc())
            .limit(20)
        )
    )
    return render_template(
        "progress.html", books=books_list, logs=logs, today_iso=date.today().isoformat()
    )


@app.route("/program/yeni", methods=["GET", "POST"])
@login_required
def schedule_create():
    if request.method == "GET":
        return render_schedule_builder()

    intent = request.form.get("intent")
    if intent == "configure":
        try:
            start_date = parse_iso_date(request.form.get("start_date"), "Başlangıç tarihi")
            end_date = parse_iso_date(request.form.get("end_date"), "Bitiş tarihi")
            if start_date > end_date:
                raise ValueError("Başlangıç tarihi bitiş tarihinden sonra olamaz.")
            return render_schedule_builder(start_date, end_date)
        except ValueError as error:
            flash(str(error), "error")
            return render_schedule_builder()

    if intent == "save":
        raw_json = request.form.get("schedule_json", "")
        draft_state = None
        try:
            draft_state = json.loads(raw_json)
            state, start_date, end_date = validate_builder_schedule(raw_json)
            schedule = Schedule(
                start_date=start_date,
                end_date=end_date,
                json_data=json.dumps(state, ensure_ascii=False),
            )
            db.session.add(schedule)
            db.session.commit()
            flash("Manuel çalışma programı kaydedildi.", "success")
            return redirect(url_for("view_schedule", schedule_id=schedule.id))
        except (ValueError, TypeError, json.JSONDecodeError) as error:
            flash(str(error), "error")
            if isinstance(draft_state, dict) and draft_state.get("version") == 3:
                try:
                    start_date = parse_iso_date(draft_state.get("start_date"), "Başlangıç tarihi")
                    end_date = parse_iso_date(draft_state.get("end_date"), "Bitiş tarihi")
                    if start_date <= end_date:
                        expected_days = (end_date - start_date).days + 1
                        days = draft_state.get("days")
                        if (
                            isinstance(days, list)
                            and len(days) == expected_days
                            and all(
                                isinstance(day, dict)
                                and isinstance(day.get("slots"), list)
                                for day in days
                            )
                        ):
                            return render_schedule_builder(start_date, end_date, draft_state)
                        return render_schedule_builder(start_date, end_date)
                except ValueError:
                    pass
            return render_schedule_builder()

    flash("Geçersiz program isteği.", "error")
    return render_schedule_builder()


@app.route("/programlarim")
@login_required
def schedules():
    schedule_list = list(db.session.scalars(select(Schedule).order_by(Schedule.created_at.desc())))
    return render_template("schedules.html", schedules=schedule_list)


@app.route("/programlarim/<int:schedule_id>")
@login_required
def view_schedule(schedule_id):
    schedule = db.get_or_404(Schedule, schedule_id)
    try:
        schedule_state = normalize_schedule_for_view(schedule)
    except ValueError as error:
        flash(str(error), "error")
        return redirect(url_for("schedules"))
    return render_template("schedule_view.html", schedule=schedule, schedule_state=schedule_state)


@app.post("/programlarim/<int:schedule_id>/sil")
@login_required
def delete_schedule(schedule_id):
    db.session.delete(db.get_or_404(Schedule, schedule_id))
    db.session.commit()
    flash("Program silindi.", "success")
    return redirect(url_for("schedules"))


@app.context_processor
def inject_today():
    return {"today": date.today()}


def migrate_database_schema():
    """Apply additive SQLite migrations for databases created by older releases."""
    with db.engine.begin() as connection:
        task_columns = {column["name"] for column in inspect(db.engine).get_columns("tasks")}
        if "control_date" not in task_columns:
            connection.execute(text("ALTER TABLE tasks ADD COLUMN control_date DATE"))
        if "description" not in task_columns:
            connection.execute(text("ALTER TABLE tasks ADD COLUMN description VARCHAR(250)"))
        connection.execute(text("CREATE INDEX IF NOT EXISTS ix_tasks_control_date ON tasks (control_date)"))

        book_columns = {column["name"] for column in inspect(db.engine).get_columns("books")}
        if "exam_type" not in book_columns:
            connection.execute(
                text("ALTER TABLE books ADD COLUMN exam_type VARCHAR(10) NOT NULL DEFAULT 'Yok'")
            )


with app.app_context():
    db.create_all()
    migrate_database_schema()


if __name__ == "__main__":
    app.run(debug=True)
