"""Command-line entry point for deterministic database monthly reports."""

from __future__ import annotations

import argparse
import json
import re
import sys
from datetime import date
from pathlib import Path
from typing import Optional, Sequence, Tuple

from monthly_reports.contracts import ErrorCode, RESULT_SCHEMA_VERSION
from arr_web.processing_runtime import compose_object_store
from monthly_reports.publishing import OpenpyxlWorkbookBuilder, AtomicReportPublisher
from monthly_reports.repository import DatabaseConfig, PostgresReportRepository, RepositoryError
from monthly_reports.service import MonthlyReportService, RunRequest, write_run_result


PROJECT_ROOT = Path(__file__).resolve().parents[1]
MONTH_RE = re.compile(r"^(\d{4})-(\d{2})$")


class RequestFailure(ValueError):
    pass


class SafeArgumentParser(argparse.ArgumentParser):
    def error(self, message: str) -> None:
        raise RequestFailure(message)


def _parse_month(value: str) -> Tuple[int, int]:
    match = MONTH_RE.fullmatch(value)
    if not match:
        raise RequestFailure("month must use YYYY-MM")
    year, month = int(match.group(1)), int(match.group(2))
    if month < 1 or month > 12:
        raise RequestFailure("month is invalid")
    return year, month


def _parse_date(value: str) -> date:
    try:
        return date.fromisoformat(value)
    except ValueError:
        raise RequestFailure("as-of date must use YYYY-MM-DD") from None


def _parser() -> SafeArgumentParser:
    parser = SafeArgumentParser(prog="monthly-reports")
    subparsers = parser.add_subparsers(dest="command", required=True)
    generate = subparsers.add_parser(
        "generate",
        help="generate or explicitly rerun one database-backed monthly report",
    )
    generate.add_argument("--month", required=True, help="report month in YYYY-MM")
    generate.add_argument("--as-of", required=True, help="latest included business date")
    generate.add_argument("--output-root", help="controlled output root inside the project")
    return parser


def _failure(code: str, stage: str) -> dict:
    return {
        "schema_version": RESULT_SCHEMA_VERSION,
        "status": "failed",
        "warnings": [],
        "errors": [{"code": code, "stage": stage}],
    }


def _emit(payload: dict) -> None:
    sys.stdout.write(json.dumps(payload, ensure_ascii=False, sort_keys=True) + "\n")


def main(argv: Optional[Sequence[str]] = None) -> int:
    try:
        args = _parser().parse_args(argv)
        year, month = _parse_month(args.month)
        as_of_date = _parse_date(args.as_of)
        request = RunRequest(year, month, as_of_date)
        request.validate()
        output_root = (
            Path(args.output_root).expanduser()
            if args.output_root
            else PROJECT_ROOT / "outputs" / "monthly_reports"
        )
        if not output_root.is_absolute():
            output_root = PROJECT_ROOT / output_root
        output_root = output_root.resolve()
        repository = PostgresReportRepository(DatabaseConfig.from_environment())
        storage_runtime = compose_object_store()
        try:
            service = MonthlyReportService(
                repository,
                OpenpyxlWorkbookBuilder(),
                AtomicReportPublisher(
                    PROJECT_ROOT,
                    output_root,
                    object_store=storage_runtime.object_store,
                ),
                output_root / ".staging",
            )
            result = service.run(request)
        finally:
            storage_runtime.close()
        result_path = (
            output_root
            / f"{year:04d}"
            / f"{month:02d}"
            / "runs"
            / f"monthly-report-{year:04d}-{month:02d}-{as_of_date.isoformat()}.result.json"
        )
        write_run_result(result_path, result)
        _emit(result.to_dict())
        return result.exit_code
    except (RequestFailure, ValueError):
        _emit(_failure(ErrorCode.REQUEST_INVALID, "request"))
        return 3
    except RepositoryError as error:
        code = (
            ErrorCode.REQUEST_INVALID
            if error.code == ErrorCode.REQUEST_INVALID
            else ErrorCode.DATABASE_FAILED
        )
        _emit(_failure(code, "configuration"))
        return 3 if code == ErrorCode.REQUEST_INVALID else 4
    except Exception:
        _emit(_failure(ErrorCode.INTERNAL_ERROR, "internal"))
        return 4


if __name__ == "__main__":
    raise SystemExit(main())
