import datetime as dt
from datetime import datetime, timezone
from decimal import Decimal

from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.billing.models.invoice import Invoice
from src.apps.onboarding.schemas.requests import OnboardingCompleteRequest
from src.apps.tenants.models.account import Account
from src.apps.tenants.models.branding_config import BrandingConfig
from src.core.constants import AccountStatus, PLAN_PRICES_CAD, SubscriptionPlan, TRIAL_DAYS


class OnboardingService:
    def __init__(self, db: AsyncSession):
        self.db = db

    async def get_account_status(self, tenant_id: str) -> dict:
        """Return current account status and onboarding_completed_at timestamp."""
        result = await self.db.execute(
            select(Account).where(Account.id == tenant_id)
        )
        account = result.scalar_one_or_none()
        if not account:
            return {
                "account_status": AccountStatus.NEW.value,
                "onboarding_completed_at": None,
                "organization_name": None,
                "cemetery_type": None,
                "address": None,
                "contact_email": None,
            }
        return {
            "account_status": account.status,
            "onboarding_completed_at": account.onboarding_completed_at,
            "organization_name": account.organization_name,
            "cemetery_type": account.cemetery_type,
            "address": account.address,
            "contact_email": account.contact_email,
        }

    async def complete_onboarding(
        self, tenant_id: str, data: OnboardingCompleteRequest
    ) -> dict:
        """
        Finalise onboarding for the tenant account.

        - Updates Account with organisation details and transitions status to ACTIVE.
        - Upserts BrandingConfig with the chosen public site name, accent colour, and logo.
        - Creates the first subscription invoice automatically.
        - Idempotent: if the account is already active the current status is returned
          without re-applying changes.
        """
        result = await self.db.execute(
            select(Account).where(Account.id == tenant_id)
        )
        account = result.scalar_one_or_none()

        # Idempotency: if already active, just return the current state.
        if account and account.status == AccountStatus.ACTIVE.value:
            return {
                "account_status": account.status,
                "onboarding_completed_at": account.onboarding_completed_at,
            }

        now = datetime.now(timezone.utc)

        if account:
            account.organization_name = data.organization_name
            account.cemetery_type = data.cemetery_type.value
            account.address = data.address
            account.contact_email = data.contact_email
            account.status = AccountStatus.ACTIVE.value
            account.onboarding_completed_at = now
        else:
            # Should never happen in practice — account is created before users
            # are provisioned — but guard defensively.
            account = Account(
                id=tenant_id,
                organization_name=data.organization_name,
                cemetery_type=data.cemetery_type.value,
                address=data.address,
                contact_email=data.contact_email,
                status=AccountStatus.ACTIVE.value,
                onboarding_completed_at=now,
            )
            self.db.add(account)

        await self.db.flush()

        # Upsert BrandingConfig
        branding_result = await self.db.execute(
            select(BrandingConfig).where(BrandingConfig.account_id == tenant_id)
        )
        branding = branding_result.scalar_one_or_none()

        if branding:
            branding.public_site_name = data.public_site_name
            branding.accent_color = data.accent_color
            branding.logo_url = data.logo_url
        else:
            branding = BrandingConfig(
                account_id=tenant_id,
                public_site_name=data.public_site_name,
                accent_color=data.accent_color,
                logo_url=data.logo_url,
            )
            self.db.add(branding)

        await self.db.flush()

        # Auto-create first subscription invoice
        await self._create_first_invoice(account, now)

        return {
            "account_status": account.status,
            "onboarding_completed_at": account.onboarding_completed_at,
        }

    async def _create_first_invoice(self, account: Account, now: datetime) -> None:
        """Create the first subscription invoice for a newly activated tenant."""
        plan_key = account.plan or SubscriptionPlan.PROFESSIONAL.value
        try:
            plan_enum = SubscriptionPlan(plan_key)
        except ValueError:
            plan_enum = SubscriptionPlan.PROFESSIONAL
        amount = Decimal(str(PLAN_PRICES_CAD.get(plan_enum, 349.00)))

        # Sequence number = count of existing invoices + 1
        seq_result = await self.db.execute(
            select(func.count(Invoice.id)).where(Invoice.tenant_id == account.id)
        )
        seq = (seq_result.scalar() or 0) + 1
        inv_number = f"INV-{now.year}-{seq:04d}"

        invoice = Invoice(
            tenant_id=account.id,
            invoice_number=inv_number,
            status="outstanding",
            total_amount=amount,
            paid_amount=Decimal("0"),
            balance_due=amount,
            due_date=(now + dt.timedelta(days=TRIAL_DAYS)).date(),
            purchaser_name=account.organization_name,
            purchaser_email=account.contact_email,
        )
        self.db.add(invoice)
        await self.db.flush()
