import json
from datetime import date, timedelta
from calendar import monthrange
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify

from app import db
from src.models import Article, Issue, Image
from src.cover import generate_cover
from src.epub_builder import build_epub
import config

publish_bp = Blueprint("publish", __name__)


def _get_week_bounds(d: date) -> tuple[date, date]:
    week_start = d - timedelta(days=d.weekday())
    week_end = week_start + timedelta(days=6)
    return week_start, week_end


def _calendar_data(year: int, month: int) -> list[dict]:
    """Build calendar grid data for a given month."""
    first_day = date(year, month, 1)
    _, days_in_month = monthrange(year, month)

    weeks = []
    current = first_day - timedelta(days=first_day.weekday())

    while current.month <= month or (current + timedelta(days=6)).month <= month or current < first_day:
        week_start = current
        week_end = current + timedelta(days=6)
        iso_week = week_start.isocalendar()[1]

        week_articles = Article.query.filter(
            Article.pub_date >= str(week_start),
            Article.pub_date < str(week_end + timedelta(days=1)),
        ).all()
        article_count = len(week_articles)
        article_dates = {a.pub_date.date() for a in week_articles}

        days = []
        for i in range(7):
            d = current + timedelta(days=i)
            days.append({
                "day": d.day,
                "date": d.isoformat(),
                "in_month": d.month == month,
                "has_articles": d in article_dates,
            })

        weeks.append({
            "iso_week": iso_week,
            "week_start": week_start.isoformat(),
            "week_end": week_end.isoformat(),
            "days": days,
            "article_count": article_count,
        })

        current += timedelta(days=7)
        if current.month > month and current.year >= year:
            if not any(d["in_month"] for d in days):
                weeks.pop()
            break

    return weeks


@publish_bp.route("/publish", methods=["GET"])
def index():
    today = date.today()
    cal_year = request.args.get("cal_year", today.year, type=int)
    cal_month = request.args.get("cal_month", today.month, type=int)

    week_start, week_end = _get_week_bounds(today)
    calendar_weeks = _calendar_data(cal_year, cal_month)

    articles = (
        Article.query
        .filter(
            Article.pub_date >= str(week_start),
            Article.pub_date < str(week_end + timedelta(days=1)),
        )
        .order_by(Article.pub_date.asc())
        .all()
    )

    all_articles = (
        Article.query
        .order_by(Article.pub_date.desc())
        .all()
    )

    return render_template(
        "publish.html",
        articles=articles,
        all_articles=all_articles,
        week_start=week_start,
        week_end=week_end,
        calendar_weeks=calendar_weeks,
        cal_year=cal_year,
        cal_month=cal_month,
        cal_month_name=date(cal_year, cal_month, 1).strftime("%B %Y"),
    )


@publish_bp.route("/publish/articles", methods=["GET"])
def articles_api():
    start = request.args.get("start")
    end = request.args.get("end")
    if not start or not end:
        return jsonify([])

    try:
        start_date = date.fromisoformat(start)
        end_date = date.fromisoformat(end)
    except ValueError:
        return jsonify([])

    articles = (
        Article.query
        .filter(
            Article.pub_date >= str(start_date),
            Article.pub_date < str(end_date + timedelta(days=1)),
        )
        .order_by(Article.pub_date.asc())
        .all()
    )

    return jsonify([
        {
            "id": a.id,
            "title": a.title,
            "author": a.author,
            "pub_date": a.pub_date.strftime("%b %d, %Y"),
            "categories": json.loads(a.categories),
        }
        for a in articles
    ])


@publish_bp.route("/publish/calendar", methods=["GET"])
def calendar_api():
    year = request.args.get("year", type=int)
    month = request.args.get("month", type=int)
    if not year or not month:
        return jsonify([])
    return jsonify(_calendar_data(year, month))


@publish_bp.route("/publish", methods=["POST"])
def create_issue():
    week_start_str = request.form.get("week_start")
    week_end_str = request.form.get("week_end")
    cover_method = request.form.get("cover_method", "text")
    issue_type = request.form.get("issue_type", "weekly")
    included_ids = request.form.getlist("article_ids", type=int)

    if not included_ids:
        flash("No articles selected.", "error")
        return redirect(url_for("publish.index"))

    week_start = date.fromisoformat(week_start_str)
    week_end = date.fromisoformat(week_end_str)

    articles_for_issue = (
        Article.query.filter(Article.id.in_(included_ids))
        .order_by(Article.pub_date.asc())
        .all()
    )
    headlines = [
        a.title for a in articles_for_issue
        if "Obituaries" not in json.loads(a.categories)
    ]

    image_paths = []
    for a in articles_for_issue:
        if "Obituaries" in json.loads(a.categories):
            continue
        first_image = Image.query.filter_by(article_id=a.id).first()
        if first_image:
            image_paths.append(first_image.local_path)

    try:
        cover_path = generate_cover(
            cover_method, config.ISSUES_DIR, week_start, week_end,
            headlines, image_paths
        )
        epub_path = build_epub(
            week_start, week_end, included_ids, cover_path,
            config.ISSUES_DIR, issue_type=issue_type
        )
    except Exception as e:
        flash(f"Error generating issue: {e}", "error")
        return redirect(url_for("publish.index"))

    all_week_articles = (
        Article.query
        .filter(
            Article.pub_date >= str(week_start),
            Article.pub_date < str(week_end + timedelta(days=1)),
        )
        .all()
    )
    all_ids = {a.id for a in all_week_articles}
    excluded_ids = list(all_ids - set(included_ids))

    issue = Issue(
        week_start=week_start,
        week_end=week_end,
        cover_method=cover_method,
        cover_path=cover_path,
        epub_path=epub_path,
        article_ids=json.dumps(included_ids),
        excluded_article_ids=json.dumps(excluded_ids),
        status="published",
        issue_type=issue_type,
    )
    db.session.add(issue)
    db.session.commit()

    flash(f"Issue published! {len(included_ids)} articles included.")
    return redirect(url_for("issues.index"))
