#!/usr/bin/env python3
"""Provision a full tenant environment for the SaaS platform.

The script creates:
1. A tenant data-plane database named ``tenant_<tenant>``
2. Bootstrap tables inside that tenant database
3. An initial admin user in the tenant database
4. Control-plane records for the tenant, app enablement, and default limits
5. Per-tenant storage and log directories

Existing applications under ``/apps`` remain untouched.
"""

from __future__ import annotations

import argparse
import json
from pathlib import Path

from sqlalchemy import create_engine, select, text
from sqlalchemy.orm import Session, sessionmaker

from config.base import Config
from config.control_plane import get_control_plane_engine
from config.db import build_database_url
from config.db_router import build_tenant_db_name, sanitize_routing_key
from platform.tenants.bootstrap_models import TenantAppConfig, TenantBootstrapBase, TenantUsageRecord, TenantUser
from platform.tenants.models import App, Base, Plan, Tenant, TenantApp, TenantLimit


BASE_DIR = Path(__file__).resolve().parents[1]
STORAGE_ROOT = BASE_DIR / "storage" / "tenants"
LOGS_ROOT = BASE_DIR / "logs" / "tenants"


def _parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Provision a tenant environment.")
    parser.add_argument("--tenant", required=True, help="Tenant slug. Allowed: ^[a-z0-9_]+$")
    parser.add_argument("--email", required=True, help="Initial tenant admin email.")
    return parser.parse_args()


def _provision_database_url() -> str:
    if not Config.PROVISION_DB_USER:
        raise RuntimeError("Missing PROVISION_DB_USER")
    return (
        f"{Config.DB_ENGINE}://{Config.PROVISION_DB_USER}:{Config.PROVISION_DB_PASSWORD}"
        f"@{Config.PROVISION_DB_HOST}:{Config.PROVISION_DB_PORT}/mysql"
    )


def _create_database(db_name: str) -> None:
    engine = create_engine(_provision_database_url(), future=True)
    with engine.begin() as conn:
        conn.execute(text(f"CREATE DATABASE IF NOT EXISTS `{db_name}`"))


def _ensure_tenant_database_schema(db_name: str, admin_email: str) -> None:
    tenant_engine = create_engine(build_database_url(db_name), future=True)
    TenantBootstrapBase.metadata.create_all(tenant_engine)
    session_factory = sessionmaker(bind=tenant_engine, autocommit=False, autoflush=False, future=True)

    with session_factory() as session:
        _upsert_tenant_admin(session, admin_email)
        _upsert_tenant_app_config(session)
        _seed_usage_marker(session)
        session.commit()


def _upsert_tenant_admin(session: Session, admin_email: str) -> None:
    existing = session.execute(
        select(TenantUser).where(TenantUser.email == admin_email.strip().lower()).limit(1)
    ).scalar_one_or_none()
    if existing is None:
        session.add(TenantUser(email=admin_email.strip().lower(), role="admin"))
        return
    existing.role = "admin"
    existing.is_active = True


def _upsert_tenant_app_config(session: Session) -> None:
    defaults = {
        "wp_invoices.enabled": {"enabled": True},
        "usage.limits.default": {"requests_per_minute": 120, "monthly_jobs": 1000},
    }
    for key, payload in defaults.items():
        record = session.get(TenantAppConfig, key)
        if record is None:
            record = TenantAppConfig(config_key=key)
            session.add(record)
        record.config_json = payload
        record.config_value = json.dumps(payload, sort_keys=True)


def _seed_usage_marker(session: Session) -> None:
    existing = session.execute(
        select(TenantUsageRecord).where(TenantUsageRecord.metric_name == "tenant.provisioned").limit(1)
    ).scalar_one_or_none()
    if existing is None:
        session.add(
            TenantUsageRecord(
                metric_name="tenant.provisioned",
                metric_value="1",
                metadata_json={"source": "scripts.provision_tenant"},
            )
        )


def _ensure_control_plane_records(*, tenant_slug: str, admin_email: str, db_name: str) -> None:
    engine = get_control_plane_engine()
    Base.metadata.create_all(engine)
    session_factory = sessionmaker(bind=engine, autocommit=False, autoflush=False, future=True)

    with session_factory() as session:
        _ensure_plan(session)
        _ensure_app_catalog(session)
        tenant = _upsert_tenant(session, tenant_slug=tenant_slug, db_name=db_name)
        _upsert_tenant_app(session, tenant_id=tenant.tenant_id, app_id="wp_invoices")
        _upsert_default_limits(session, tenant_id=tenant.tenant_id, app_id="wp_invoices")
        session.commit()


def _ensure_plan(session: Session) -> None:
    plan = session.get(Plan, "dev")
    if plan is None:
        plan = Plan(
            plan_id="dev",
            code="dev",
            name="Development",
            description="Default development plan for newly provisioned tenants.",
            price_monthly=0,
            currency="AUD",
        )
        session.add(plan)


def _ensure_app_catalog(session: Session) -> None:
    app = session.get(App, "wp_invoices")
    if app is None:
        app = App(
            app_id="wp_invoices",
            display_name="WP Invoices",
            module_path="apps.wp_invoices",
            route_prefix="/api/v1/wp_invoices",
            is_active=True,
        )
        session.add(app)


def _upsert_tenant(session: Session, *, tenant_slug: str, db_name: str) -> Tenant:
    tenant = session.get(Tenant, tenant_slug)
    if tenant is None:
        tenant = Tenant(
            tenant_id=tenant_slug,
            slug=tenant_slug,
            name=tenant_slug.replace("_", " ").title(),
            status="active",
            is_active=True,
            plan_id="dev",
            data_db_name=db_name,
            storage_key=tenant_slug,
            metadata_json={"provisioned_by": "scripts.provision_tenant"},
        )
        session.add(tenant)
        return tenant

    tenant.status = "active"
    tenant.is_active = True
    tenant.plan_id = "dev"
    tenant.data_db_name = db_name
    tenant.storage_key = tenant_slug
    metadata = dict(tenant.metadata_json or {})
    metadata["provisioned_by"] = "scripts.provision_tenant"
    tenant.metadata_json = metadata
    return tenant


def _upsert_tenant_app(session: Session, *, tenant_id: str, app_id: str) -> None:
    stmt = select(TenantApp).where(TenantApp.tenant_id == tenant_id, TenantApp.app_id == app_id).limit(1)
    tenant_app = session.execute(stmt).scalar_one_or_none()
    if tenant_app is None:
        tenant_app = TenantApp(
            tenant_id=tenant_id,
            app_id=app_id,
            is_enabled=True,
            config_json={"enabled": True},
        )
        session.add(tenant_app)
        return
    tenant_app.is_enabled = True
    tenant_app.config_json = {"enabled": True}


def _upsert_default_limits(session: Session, *, tenant_id: str, app_id: str) -> None:
    defaults = {
        "requests_per_minute": {"limit_value": 120, "window_seconds": 60, "overage_policy": "block"},
        "monthly_jobs": {"limit_value": 1000, "window_seconds": None, "overage_policy": "track"},
    }
    for limit_name, payload in defaults.items():
        stmt = (
            select(TenantLimit)
            .where(
                TenantLimit.tenant_id == tenant_id,
                TenantLimit.app_id == app_id,
                TenantLimit.limit_name == limit_name,
            )
            .limit(1)
        )
        record = session.execute(stmt).scalar_one_or_none()
        if record is None:
            record = TenantLimit(tenant_id=tenant_id, app_id=app_id, limit_name=limit_name)
            session.add(record)
        record.limit_value = payload["limit_value"]
        record.window_seconds = payload["window_seconds"]
        record.overage_policy = payload["overage_policy"]
        record.metadata_json = {"default": True}


def _ensure_filesystem(tenant_slug: str) -> tuple[Path, Path]:
    storage_path = STORAGE_ROOT / tenant_slug
    logs_path = LOGS_ROOT / tenant_slug
    storage_path.mkdir(parents=True, exist_ok=True)
    logs_path.mkdir(parents=True, exist_ok=True)
    return storage_path, logs_path


def main() -> int:
    args = _parse_args()
    tenant_slug = sanitize_routing_key(args.tenant)
    if tenant_slug is None:
        raise RuntimeError("tenant is required")

    admin_email = args.email.strip().lower()
    if not admin_email:
        raise RuntimeError("email is required")

    db_name = build_tenant_db_name(tenant_slug)
    storage_path, logs_path = _ensure_filesystem(tenant_slug)
    _create_database(db_name)
    _ensure_tenant_database_schema(db_name, admin_email)
    _ensure_control_plane_records(tenant_slug=tenant_slug, admin_email=admin_email, db_name=db_name)

    summary = {
        "tenant": tenant_slug,
        "db": db_name,
        "storage": str(storage_path),
        "logs": str(logs_path),
        "status": "created",
    }
    print(json.dumps(summary, indent=2))
    return 0


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