"""Artifacts domain — AI-generated markdown documents (v1 port).

Files live at <DATA_DIR>/artifacts/<filename>; DB rows carry metadata.
Kind vocabulary and list grouping match v1 (main.py:7184-7260, 8060-8104).
"""
from __future__ import annotations

import os
import re
from datetime import date as date_type, datetime, timezone

import markdown as md
from sqlalchemy import select
from sqlalchemy.orm import Session

from ..config import get_settings
from ..errors import AppError
from ..models.history import Artifact
from . import audit

ARTIFACT_KINDS = [
    "planting-plan", "fertilization-schedule", "shopping-list",
    "watering-plan", "other",
]

ARTIFACT_NOT_FOUND = "artifact_not_found"
ARTIFACT_INVALID = "artifact_invalid"


def _artifacts_dir() -> str:
    return os.path.join(get_settings().data_dir, "artifacts")


def _read_file(filename: str) -> str:
    path = os.path.join(_artifacts_dir(), filename)
    if not os.path.isfile(path):
        return ""
    with open(path, encoding="utf-8") as f:
        return f.read()


def _slugify(title: str) -> str:
    out = []
    for c in title.lower():
        if c.isalnum():
            out.append(c)
        elif c in " -_":
            out.append("-")
    slug = "".join(out).strip("-")
    while "--" in slug:
        slug = slug.replace("--", "-")
    return slug or "artifact"


def _row(a: Artifact) -> dict:
    return {
        "id": a.id,
        "kind": a.kind,
        "title": a.title,
        "filename": a.filename,
        "generated_at": a.generated_at.isoformat() if a.generated_at else None,
        "notes": a.notes or "",
    }


def list_artifacts(db: Session, garden_id: int) -> dict:
    rows = db.scalars(
        select(Artifact)
        .where(Artifact.garden_id == garden_id)
        .order_by(Artifact.generated_at.desc(), Artifact.id.desc())
    ).all()
    by_kind: dict = {}
    for a in rows:
        by_kind.setdefault(a.kind, []).append(_row(a))
    kind_order = [k for k in ARTIFACT_KINDS if k in by_kind]
    return {
        "artifacts": [_row(a) for a in rows],
        "by_kind": by_kind,
        "kind_order": kind_order,
    }


def get_artifact(db: Session, garden_id: int, artifact_id: int) -> dict:
    a = db.scalar(
        select(Artifact).where(
            Artifact.id == artifact_id, Artifact.garden_id == garden_id
        )
    )
    if a is None:
        raise AppError(ARTIFACT_NOT_FOUND, f"artifact {artifact_id} not found", 404)
    content_md = _read_file(a.filename)
    content_html = (
        md.markdown(
            content_md,
            extensions=["tables", "fenced_code", "nl2br", "sane_lists"],
        )
        if content_md
        else ""
    )
    return {**_row(a), "content_md": content_md, "content_html": content_html}


def create_artifact(
    db: Session, garden_id: int, *, kind: str, title: str, content_md: str,
    notes: str = "", generated_at: str | None = None,
) -> dict:
    if kind not in ARTIFACT_KINDS:
        raise AppError(ARTIFACT_INVALID, f"kind must be one of {ARTIFACT_KINDS}", 400)
    if not title.strip() or not content_md.strip():
        raise AppError(ARTIFACT_INVALID, "title and content_md are required", 400)
    base = _slugify(title)
    filename = f"{base}.md"
    i = 2
    os.makedirs(_artifacts_dir(), exist_ok=True)
    while os.path.exists(os.path.join(_artifacts_dir(), filename)):
        filename = f"{base}-{i}.md"
        i += 1
    with open(os.path.join(_artifacts_dir(), filename), "w", encoding="utf-8") as f:
        f.write(content_md)
    if generated_at and re.fullmatch(r"\d{4}-\d{2}-\d{2}", generated_at):
        gen = date_type.fromisoformat(generated_at)
    else:
        gen = datetime.now(timezone.utc).date()
    a = Artifact(
        garden_id=garden_id, kind=kind, title=title.strip(),
        filename=filename, generated_at=gen, notes=notes,
    )
    db.add(a)
    db.flush()
    audit.log_add(db, garden_id, "artifact", a.id, a.title)
    return _row(a)


def delete_artifact(db: Session, garden_id: int, artifact_id: int) -> None:
    a = db.scalar(
        select(Artifact).where(
            Artifact.id == artifact_id, Artifact.garden_id == garden_id
        )
    )
    if a is None:
        raise AppError(ARTIFACT_NOT_FOUND, f"artifact {artifact_id} not found", 404)
    path = os.path.join(_artifacts_dir(), a.filename)
    if os.path.isfile(path):
        try:
            os.remove(path)
        except OSError:
            pass
    label = a.title
    db.delete(a)
    audit.log_delete(db, garden_id, "artifact", artifact_id, label)
