"""Imports a merchant-uploaded CSV into the shared catalog table.

The tail end of the flow is identical to sync_source in catalog/sync.py --
normalise, persist, delete_stale -- there is just no fetch in front of it. A
CSV row carries no product URL, so normalise() flags it in missing_fields
rather than rejecting the product outright; that is intended, not a bug.
"""
import logging
from datetime import datetime, timezone

from app.services.catalog.csv_source import parse_products
from app.services.catalog.normalise import normalise
from app.services.catalog.sync import delete_stale, persist
from app.services.infra.database import (
    count_new_products, get_setting, migrate_products_table, update_setting,
)
from app.services.infra.quotas import BRANDFORGE, check_quota

logger = logging.getLogger(__name__)

SOURCE_KIND = "csv"


def import_csv(tenant_id: str, text: str, source_ref: str, currency: str = None,
               dry_run: bool = False) -> dict:
    run_started = datetime.now(timezone.utc)

    source_products, errors = parse_products(text, source_ref, currency)

    context = {
        "shop_name": None,
        "primary_domain": None,
        "url_template": None,
        "currency": currency,
        "known_brands": [],
    }

    # Same rule sync_source applies: the first connected source defines the
    # tenant's reference currency. Without it every price is unconvertible, so
    # normalise() flags the whole catalogue price_reference and pairing skips
    # every product -- an import that appears to succeed and produces nothing.
    if not dry_run:
        reference_currency = get_setting(tenant_id, "reference_currency")
        # Only an upload that declares a currency may establish the tenant's
        # reference: setting it from nothing would leave a null on record and
        # make every later comparison unconvertible.
        # Fall back to whatever the file itself declared: with a Currency
        # column the upload need not repeat it as a form field, and a tenant
        # whose reference is never set has every price left unconvertible.
        declared = currency or next(
            (p["currency"] for p in source_products if p.get("currency")), None)
        if not reference_currency and declared:
            reference_currency = declared
            update_setting(tenant_id, "reference_currency",
                           lambda _: reference_currency)
        context["reference_currency"] = reference_currency
    normalised = [n for n in
                  (normalise(sp, context) for sp in source_products)
                  if n is not None]

    if dry_run:
        return {
            "dry_run": True,
            "products": len(source_products),
            "imported": len(normalised),
            "errors": errors,
        }

    # Before persist, not after: writing what fits and dropping the rest would
    # leave delete_stale to reap the remainder on the next run, turning a plan
    # limit into deleted products. The whole upload is refused instead.
    #
    # Only genuinely new products count. Re-uploading the same file updates
    # rows rather than adding them, and a merchant who changes one price should
    # not be told their catalogue is full.
    check_quota(tenant_id, "product_count", product_name=BRANDFORGE,
                requested_amount=count_new_products(
                    tenant_id, [p["product_key"] for p in normalised]))

    migrate_products_table(tenant_id)
    persist(tenant_id, normalised, SOURCE_KIND, source_ref, run_started)
    # Scoped to (source_kind, source_ref): only rows from a previous upload
    # of this same file are removed. Any other source's rows share the table
    # but never match this filter.
    replaced = delete_stale(tenant_id, SOURCE_KIND, source_ref, run_started)

    if errors:
        logger.warning("csv import %s for %s: %d row error(s)",
                       source_ref, tenant_id, len(errors))

    return {
        "dry_run": False,
        "products": len(source_products),
        "imported": len(normalised),
        "replaced": replaced,
        "errors": errors,
    }
