import json
from pathlib import Path
from typing import Any

from app.config import settings
from app.services.store import JsonStore


class JsonRepository:
    backend = "json"

    def __init__(self) -> None:
        self.store = JsonStore()

    def list_courses(self) -> list[dict[str, Any]]:
        return self.store.load("courses", [])

    def replace_courses(self, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
        return self.store.replace_collection("courses", items)

    def list_assignments(self) -> list[dict[str, Any]]:
        return self.store.load("assignments", [])

    def replace_assignments(self, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
        return self.store.replace_collection("assignments", items)

    def list_sync_runs(self) -> list[dict[str, Any]]:
        return self.store.load("sync_runs", [])

    def save_sync_runs(self, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
        return self.store.save("sync_runs", items)

    def list_submissions(self) -> list[dict[str, Any]]:
        return self.store.load("submissions", [])

    def create_submission(self, item: dict[str, Any]) -> dict[str, Any]:
        return self.store.append_collection("submissions", item)

    def update_submission(self, submission_id: str, updates: dict[str, Any]) -> dict[str, Any] | None:
        return self.store.update_collection_item(
            "submissions",
            lambda item: str(item.get("id")) == str(submission_id),
            lambda item: {**item, **updates},
        )

    def list_recordings(self) -> list[dict[str, Any]]:
        return self.store.load("recordings", [])

    def create_recording(self, item: dict[str, Any]) -> dict[str, Any]:
        return self.store.append_collection("recordings", item)

    def update_recording(self, recording_id: str, updates: dict[str, Any]) -> dict[str, Any] | None:
        return self.store.update_collection_item(
            "recordings",
            lambda item: str(item.get("id")) == str(recording_id),
            lambda item: {**item, **updates},
        )


class PostgresRepository:
    backend = "postgres"

    def __init__(self) -> None:
        import psycopg
        from psycopg.rows import dict_row

        self._psycopg = psycopg
        self._dict_row = dict_row
        self.dsn = settings.database_url.replace("postgresql+psycopg://", "postgresql://")

    def _connect(self):
        return self._psycopg.connect(self.dsn, row_factory=self._dict_row)

    def _ensure_course(self, cur, item: dict[str, Any]) -> dict[str, Any] | None:
        course_id = item.get("class_id") or item.get("course_id")
        if not course_id:
            return None
        cur.execute("SELECT id, d2l_course_id, name FROM schoolhouse_course WHERE d2l_course_id = %s", (course_id,))
        row = cur.fetchone()
        if row:
            return row
        course_name = item.get("class_name") or item.get("course_name") or str(course_id)
        cur.execute(
            """
            INSERT INTO schoolhouse_course (d2l_course_id, name, last_synced_at)
            VALUES (%s, %s, NOW())
            RETURNING id, d2l_course_id, name
            """,
            (course_id, course_name),
        )
        return cur.fetchone()

    def _ensure_assignment(self, cur, item: dict[str, Any]) -> dict[str, Any] | None:
        course = self._ensure_course(cur, item)
        if not course:
            return None
        assignment_id = item.get("assignment_id")
        assignment_name = item.get("assignment_name") or item.get("title")
        if assignment_id:
            cur.execute(
                "SELECT id, title FROM schoolhouse_assignment WHERE course_id = %s AND d2l_assignment_key = %s LIMIT 1",
                (course["id"], assignment_id),
            )
            row = cur.fetchone()
            if row:
                return row
        if assignment_name:
            cur.execute(
                "SELECT id, title FROM schoolhouse_assignment WHERE course_id = %s AND title = %s LIMIT 1",
                (course["id"], assignment_name),
            )
            row = cur.fetchone()
            if row:
                return row
            cur.execute(
                """
                INSERT INTO schoolhouse_assignment (course_id, d2l_assignment_key, title, source, status, created_at, updated_at)
                VALUES (%s, %s, %s, %s, %s, NOW(), NOW())
                RETURNING id, title
                """,
                (course["id"], assignment_id, assignment_name, item.get("source", "schoolhouse-intake"), item.get("status", "assigned")),
            )
            return cur.fetchone()
        return None

    def list_courses(self) -> list[dict[str, Any]]:
        with self._connect() as conn, conn.cursor() as cur:
            cur.execute("SELECT id::text AS id, d2l_course_id, name, code, semester, last_synced_at FROM schoolhouse_course ORDER BY name")
            return list(cur.fetchall())

    def replace_courses(self, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
        with self._connect() as conn, conn.cursor() as cur:
            for item in items:
                cur.execute(
                    """
                    INSERT INTO schoolhouse_course (d2l_course_id, name, code, semester, last_synced_at)
                    VALUES (%s, %s, %s, %s, NOW())
                    ON CONFLICT (d2l_course_id)
                    DO UPDATE SET name = EXCLUDED.name, code = EXCLUDED.code, semester = EXCLUDED.semester, last_synced_at = NOW()
                    """,
                    (item.get("d2l_course_id"), item.get("name"), item.get("code"), item.get("semester")),
                )
            conn.commit()
        return items

    def list_assignments(self) -> list[dict[str, Any]]:
        with self._connect() as conn, conn.cursor() as cur:
            cur.execute(
                """
                SELECT a.id::text AS id,
                       c.d2l_course_id AS course_id,
                       c.name AS course_name,
                       a.title,
                       a.grade_text AS grade,
                       a.professor_comments AS comments,
                       a.status,
                       a.updated_at
                  FROM schoolhouse_assignment a
                  JOIN schoolhouse_course c ON c.id = a.course_id
                 ORDER BY c.name, a.title
                """
            )
            return list(cur.fetchall())

    def replace_assignments(self, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
        with self._connect() as conn, conn.cursor() as cur:
            for item in items:
                cur.execute("SELECT id FROM schoolhouse_course WHERE d2l_course_id = %s", (item.get("course_id"),))
                row = cur.fetchone()
                if not row:
                    continue
                course_pk = row["id"]
                cur.execute(
                    """
                    INSERT INTO schoolhouse_assignment (course_id, d2l_assignment_key, title, grade_text, professor_comments, source, status, created_at, updated_at)
                    VALUES (%s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
                    ON CONFLICT DO NOTHING
                    """,
                    (course_pk, item.get("id"), item.get("title"), item.get("grade"), item.get("comments"), item.get("source", "d2l"), item.get("status", "assigned")),
                )
                cur.execute(
                    """
                    UPDATE schoolhouse_assignment
                       SET grade_text = %s,
                           professor_comments = %s,
                           status = COALESCE(status, %s),
                           updated_at = NOW()
                     WHERE course_id = %s AND title = %s
                    """,
                    (item.get("grade"), item.get("comments"), item.get("status", "assigned"), course_pk, item.get("title")),
                )
            conn.commit()
        return items

    def list_sync_runs(self) -> list[dict[str, Any]]:
        with self._connect() as conn, conn.cursor() as cur:
            cur.execute("SELECT id::text AS id, started_at, completed_at, status, raw_snapshot_path AS snapshot_path, changes_summary_json FROM schoolhouse_d2l_sync_run ORDER BY started_at DESC")
            return list(cur.fetchall())

    def save_sync_runs(self, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
        if not items:
            return items
        last = items[-1]
        with self._connect() as conn, conn.cursor() as cur:
            cur.execute(
                "INSERT INTO schoolhouse_d2l_sync_run (completed_at, status, raw_snapshot_path, changes_summary_json) VALUES (NOW(), %s, %s, %s::jsonb)",
                (last.get("status", "completed"), last.get("snapshot_path"), json.dumps(last)),
            )
            conn.commit()
        return items

    def list_submissions(self) -> list[dict[str, Any]]:
        with self._connect() as conn, conn.cursor() as cur:
            cur.execute(
                """
                SELECT s.id::text AS id,
                       a.id::text AS assignment_id,
                       c.d2l_course_id AS class_id,
                       c.name AS class_name,
                       a.title AS assignment_name,
                       s.version_label,
                       s.original_filename,
                       s.mime_type,
                       s.status,
                       s.paperless_document_id,
                       s.paperless_title,
                       s.paperless_note_last_hash,
                       s.submitted_at,
                       s.confirmed_metadata_json,
                       s.confirmed_metadata_json->>'batch_id' AS batch_id,
                       s.confirmed_metadata_json->>'file_count' AS file_count,
                       s.confirmed_metadata_json->>'file_index' AS file_index,
                       s.confirmed_metadata_json->>'submission_kind' AS submission_kind,
                       s.confirmed_metadata_json->>'artifact_kind' AS artifact_kind,
                       s.confirmed_metadata_json->>'course_code' AS course_code,
                       s.confirmed_metadata_json->>'paper_kind' AS paper_kind,
                       s.confirmed_metadata_json->>'source' AS source,
                       s.confirmed_metadata_json->>'notes' AS notes,
                       s.confirmed_metadata_json->>'received_at' AS received_at,
                       s.confirmed_metadata_json->>'bundle_path' AS bundle_path,
                       s.confirmed_metadata_json->>'bundle_filename' AS bundle_filename,
                       s.confirmed_metadata_json->>'source_listing_path' AS source_listing_path,
                       s.confirmed_metadata_json->>'receipt_pdf_path' AS receipt_pdf_path,
                       s.confirmed_metadata_json->>'paperless_task_id' AS paperless_task_id,
                       s.confirmed_metadata_json->'file_manifest_json' AS file_manifest_json,
                       s.confirmed_metadata_json->'stored_paths' AS stored_paths,
                       s.confirmed_metadata_json->>'stored_path' AS stored_path,
                       s.confirmed_metadata_json->>'local_file' AS local_file
                  FROM schoolhouse_submission s
                  JOIN schoolhouse_assignment a ON a.id = s.assignment_id
                  JOIN schoolhouse_course c ON c.id = a.course_id
                 ORDER BY s.created_at DESC
                """
            )
            return list(cur.fetchall())

    def create_submission(self, item: dict[str, Any]) -> dict[str, Any]:
        with self._connect() as conn, conn.cursor() as cur:
            row = self._ensure_assignment(cur, item)
            if not row:
                raise ValueError("Assignment not found and could not be created in Postgres repository")
            cur.execute(
                """
                INSERT INTO schoolhouse_submission (
                  assignment_id, version_label, original_filename, mime_type, checksum_sha256,
                  submitted_at, suggested_metadata_json, confirmed_metadata_json, status
                ) VALUES (%s, %s, %s, %s, %s, NOW(), %s::jsonb, %s::jsonb, %s)
                RETURNING id::text AS id
                """,
                (
                    row["id"], item.get("version_label") or "v1", item.get("original_filename"), item.get("mime_type", "application/octet-stream"), item.get("checksum_sha256"),
                    json.dumps(item.get("paperless_handoff", {})), json.dumps(item), item.get("status", "uploaded-local"),
                ),
            )
            created = cur.fetchone()
            conn.commit()
            item["id"] = created["id"]
            return item

    def update_submission(self, submission_id: str, updates: dict[str, Any]) -> dict[str, Any] | None:
        with self._connect() as conn, conn.cursor() as cur:
            cur.execute("SELECT confirmed_metadata_json FROM schoolhouse_submission WHERE id = %s::bigint", (submission_id,))
            current = cur.fetchone()
            if not current:
                return None
            merged_metadata = {**(current.get("confirmed_metadata_json") or {}), **updates}
            cur.execute(
                """
                UPDATE schoolhouse_submission
                   SET status = COALESCE(%s, status),
                       paperless_document_id = COALESCE(%s, paperless_document_id),
                       paperless_title = COALESCE(%s, paperless_title),
                       paperless_note_last_hash = COALESCE(%s, paperless_note_last_hash),
                       paperless_note_appended_at = CASE WHEN %s::text IS NOT NULL THEN NOW() ELSE paperless_note_appended_at END,
                       confirmed_metadata_json = %s::jsonb,
                       updated_at = NOW()
                 WHERE id = %s::bigint
             RETURNING id::text AS id, status, paperless_document_id, paperless_title, paperless_note_last_hash
                """,
                (
                    updates.get("status"), updates.get("paperless_document_id"), updates.get("paperless_title"), updates.get("paperless_note_last_hash"), updates.get("paperless_note_last_hash"),
                    json.dumps(merged_metadata), submission_id,
                ),
            )
            row = cur.fetchone()
            conn.commit()
            return {**merged_metadata, **dict(row)} if row else None

    def list_recordings(self) -> list[dict[str, Any]]:
        with self._connect() as conn, conn.cursor() as cur:
            cur.execute(
                """
                SELECT r.id::text AS id,
                       c.d2l_course_id AS class_id,
                       c.name AS class_name,
                       r.assignment_id::text AS assignment_id,
                       r.course_id::text AS course_id,
                       r.session_date::text AS session_date,
                       r.original_filename,
                       r.mp3_filename,
                       r.transcription_status,
                       r.summary_status,
                       r.recording_paperless_document_id,
                       r.recording_metadata_json,
                       r.recording_metadata_json->>'stored_path' AS stored_path,
                       r.recording_metadata_json->>'mp3_path' AS mp3_path,
                       r.recording_metadata_json->>'status' AS status,
                       r.recording_metadata_json->>'conversion_status' AS conversion_status,
                       r.recording_metadata_json->>'notes' AS notes,
                       r.recording_metadata_json->>'delete_wav_after_success' AS delete_wav_after_success
                  FROM schoolhouse_recording r
                  JOIN schoolhouse_course c ON c.id = r.course_id
                 ORDER BY r.created_at DESC
                """
            )
            return list(cur.fetchall())

    def create_recording(self, item: dict[str, Any]) -> dict[str, Any]:
        with self._connect() as conn, conn.cursor() as cur:
            row = self._ensure_course(cur, item)
            if not row:
                raise ValueError("Course not found and could not be created in Postgres repository")
            cur.execute(
                """
                INSERT INTO schoolhouse_recording (
                  course_id, assignment_id, session_date, original_filename, mp3_filename, notes,
                  transcription_status, summary_status, recording_metadata_json
                ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s::jsonb)
                RETURNING id::text AS id
                """,
                (
                    row["id"], item.get("assignment_id") or None, item.get("session_date"), item.get("original_filename"),
                    Path(item.get("mp3_path")).name if item.get("mp3_path") else None, item.get("notes"), item.get("transcription_status"),
                    item.get("summary_status"), json.dumps(item),
                ),
            )
            created = cur.fetchone()
            conn.commit()
            item["id"] = created["id"]
            return item

    def update_recording(self, recording_id: str, updates: dict[str, Any]) -> dict[str, Any] | None:
        with self._connect() as conn, conn.cursor() as cur:
            cur.execute("SELECT recording_metadata_json FROM schoolhouse_recording WHERE id = %s::bigint", (recording_id,))
            current = cur.fetchone()
            if not current:
                return None
            merged_metadata = {**(current.get("recording_metadata_json") or {}), **updates}
            cur.execute(
                """
                UPDATE schoolhouse_recording
                   SET transcription_status = COALESCE(%s, transcription_status),
                       summary_status = COALESCE(%s, summary_status),
                       transcript_paperless_document_id = COALESCE(%s, transcript_paperless_document_id),
                       summary_paperless_document_id = COALESCE(%s, summary_paperless_document_id),
                       recording_paperless_document_id = COALESCE(%s, recording_paperless_document_id),
                       mp3_filename = COALESCE(%s, mp3_filename),
                       notes = COALESCE(%s, notes),
                       recording_metadata_json = %s::jsonb,
                       updated_at = NOW()
                 WHERE id = %s::bigint
             RETURNING id::text AS id, transcription_status, summary_status, recording_paperless_document_id
                """,
                (
                    updates.get("transcription_status"), updates.get("summary_status"), updates.get("transcript_paperless_document_id"),
                    updates.get("summary_paperless_document_id"), updates.get("recording_paperless_document_id"),
                    Path(updates.get("mp3_path")).name if updates.get("mp3_path") else None,
                    updates.get("notes"), json.dumps(merged_metadata), recording_id,
                ),
            )
            row = cur.fetchone()
            conn.commit()
            return {**merged_metadata, **dict(row)} if row else None


def get_repository():
    backend = getattr(settings, "storage_backend", "auto")
    if backend == "json":
        return JsonRepository()
    if settings.database_url.startswith("postgresql"):
        try:
            return PostgresRepository()
        except Exception:
            return JsonRepository()
    return JsonRepository()
