"""
PyCharter Worker -- durable job queue and worker process.

DB-only, broker-free. Run ``pycharter worker`` to process validation jobs
from the validation_jobs table. Multiple workers can run; each job is
claimed by exactly one worker (atomic claim).

Quick start::

    from pycharter.worker import Worker

    worker = Worker()
    await worker.start()
    await worker.run()

Submitting jobs (from API or any process with DB access)::

    from pycharter.worker import submit_job

    job_id = await submit_job(
        contract_name="user_schema",
        contract_version="1.0",
        data_source="/data/users.json",
    )
"""

from __future__ import annotations

import asyncio
from typing import Any

from pycharter.worker.config import WorkerConfig
from pycharter.worker.models import ClaimedJob, JobStatus, ValidationJob
from pycharter.worker.runner import Worker

__all__ = [
    "Worker",
    "WorkerConfig",
    "ValidationJob",
    "ClaimedJob",
    "JobStatus",
    "submit_job",
    "submit_job_sync",
    "get_job",
]


async def submit_job(
    contract_name: str,
    contract_version: str,
    data_source: str,
    *,
    options: dict[str, Any] | None = None,
    job_id: str | None = None,
    fires_at: Any | None = None,
    idempotency_key: str | None = None,
    max_attempts: int = 5,
    db_url: str | None = None,
) -> str:
    """Submit a validation job to the queue.

    Args:
        contract_name: Data contract name.
        contract_version: Data contract version.
        data_source: Data source path (file, S3, table, etc.).
        options: Optional validation options.
        job_id: Optional custom job id.
        fires_at: When the job becomes eligible (None = now).
        idempotency_key: Optional dedup key; duplicate submits return existing id.
        max_attempts: Max processing attempts.
        db_url: Database URL (default: PYCHARTER_DATABASE_URL env).

    Returns:
        Job id (existing id if idempotency_key duplicate).
    """
    from pycharter.worker.job_sources.database import DatabaseJobSource

    resolved = db_url
    if resolved is None:
        from pycharter.config import get_database_url

        resolved = get_database_url()
    if not resolved:
        raise ValueError("No database URL. Set PYCHARTER_DATABASE_URL or pass db_url.")

    source = DatabaseJobSource(resolved, config=None)
    try:
        job = ValidationJob(
            contract_name=contract_name,
            contract_version=contract_version,
            data_source=data_source,
            options=options,
            job_id=job_id,
            fires_at=fires_at,
            idempotency_key=idempotency_key,
            max_attempts=max_attempts,
        )
        return await source.submit(job)
    finally:
        await source.close()


def submit_job_sync(
    contract_name: str,
    contract_version: str,
    data_source: str,
    *,
    options: dict[str, Any] | None = None,
    job_id: str | None = None,
    idempotency_key: str | None = None,
    max_attempts: int = 5,
    db_url: str | None = None,
) -> str:
    """Synchronous submit_job for use from non-async code."""
    return asyncio.run(
        submit_job(
            contract_name=contract_name,
            contract_version=contract_version,
            data_source=data_source,
            options=options,
            job_id=job_id,
            idempotency_key=idempotency_key,
            max_attempts=max_attempts,
            db_url=db_url,
        )
    )


async def get_job(job_id: str, db_url: str | None = None) -> dict[str, Any] | None:
    """Get job status and result by id. Returns None if not found."""
    import os

    from pycharter.worker.job_sources.database import DatabaseJobSource

    resolved = db_url or os.environ.get("PYCHARTER_DATABASE_URL")
    if not resolved:
        from pycharter.config import get_database_url

        resolved = get_database_url()
    if not resolved:
        return None
    source = DatabaseJobSource(resolved, config=None)
    try:
        return await source.get_job(job_id)
    finally:
        await source.close()
