# companies/middleware.py
"""
CompanyMiddleware — Phase 4 of SwiftPOS SaaS transformation.

Responsibility:
  1. For every incoming request, resolve `request.company` from the URL slug.
  2. Enforce that the company is active/not-expired before serving.
  3. Silently skip resolution for:
       - Superadmin requests (they work across all companies)
       - Non-company-scoped URLs (admin/, static/, media/)
       - API requests that pass company via HTTP header

request.company is available in every view and every template
(via context_processors.swiftpos_settings).
"""

from django.shortcuts import redirect
from django.http import Http404, HttpResponseForbidden
from django.urls import resolve, Resolver404
from django.utils.deprecation import MiddlewareMixin

from companies.models import Company


_BYPASS_PREFIXES = (
    # ── Django admin (read from settings to respect any URL rename) ──────────
    # Populated at class-init time below; kept here for reference
    '/static/',
    '/media/',
    '/__debug__/',       # Django Debug Toolbar
    '/superadmin/',      # Superadmin management panel
    '/accounts/login/',
    '/accounts/logout/',
    '/accounts/password_reset/',
    '/accounts/reset/',
    '/company/inactive/',
    # ── SaaS public & auth-exempt paths ──────────────────
    '/register/',        # Self-signup
    '/pricing/',         # Public pricing page
    '/webhooks/',        # Monnify webhook (HMAC-verified internally)
    '/subscription/choose-plan/',   # Allow expired companies to pick a plan
    '/subscription/checkout/',      # Allow checkout even if expired
    '/subscription/payment/',       # Payment callback
    '/subscription/manage/',        # Subscription management
    '/subscription/billing/',       # Billing history
    '/subscription/promo/',         # Promo validation AJAX
    # ── Mobile unauthenticated endpoints ─────────────────
    '/api/',
)


class CompanyMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # Dynamically include the Django admin URL prefix so renaming it
        # (e.g. admin/ → admin000/) never causes it to be treated as a slug.
        from django.conf import settings as dj_settings
        admin_url = getattr(dj_settings, 'DJANGO_ADMIN_URL', 'admin000')
        self._bypass = _BYPASS_PREFIXES + (f'/{admin_url.strip("/")}/',)

    def __call__(self, request):
        request.company = None  # safe default

        path = request.path_info

        # ── Fast bypass for non-company paths ──────────────────────────────────
        if any(path.startswith(prefix) for prefix in self._bypass):
            return self.get_response(request)

        # ── Try to extract slug from URL path: /<slug>/... ─────────────────────
        slug = self._extract_slug(path)

        # ── API / Header Fallback ──────────────────────────────────────────────
        if not slug and path.startswith('/api/'):
            # Allow API clients to provide company slug via header
            slug = request.headers.get('X-Company-Slug')

        if slug:
            company = self._resolve_company(slug)
            if company is None:
                # Unknown slug → 404 (for web) or JsonResponse 404 (for API)
                if '/api/' in path:
                    from django.http import JsonResponse
                    return JsonResponse({'detail': f"Company '{slug}' not found."}, status=404)
                raise Http404(f"Company '{slug}' not found.")

            request.company = company

            # If it was a URL slug (not header), do rewrite
            if not request.headers.get('X-Company-Slug') and slug in path:
                script_name = f"/{slug}"
                path_info = path[len(script_name):]
                if not path_info.startswith('/'):
                    path_info = '/' + path_info
                    
                request.META['SCRIPT_NAME'] = script_name
                request.path_info = path_info
                
                from django.urls import set_script_prefix
                set_script_prefix(script_name + '/')

            # ── Check if company is accessible (active & not expired) ──────────
            if request.user.is_authenticated and request.user.is_superuser:
                pass  # bypass activation check for superadmin
            else:
                if company.expiry_date is None:
                    import logging
                    _logger = logging.getLogger(__name__)
                    _logger.warning(f"SECURITY: Company '{company.slug}' accessed with NULL expiry_date. This bypasses subscription enforcement.")

                if not company.is_accessible:
                    if request.path_info.startswith('/api/'):
                        from django.http import JsonResponse
                        return JsonResponse({'detail': 'Company subscription is inactive or expired.'}, status=403)
                    
                    _INACTIVE_BYPASS = (
                        '/company/inactive/',
                        '/accounts/logout/',
                        '/accounts/login/',
                        '/accounts/password_reset/',
                        '/accounts/reset/',
                        '/subscription/manage/',
                        '/subscription/pricing/',
                        '/subscription/choose-plan/',
                        '/subscription/checkout/',
                        '/subscription/payment/callback/',
                    )
                    
                    if not any(request.path_info.startswith(bp) for bp in _INACTIVE_BYPASS):
                        return redirect('company_inactive')

        # ── Final Fallback: Resolve company from User context if still None ────
        if request.company is None and request.user.is_authenticated:
            user_company = getattr(request.user, 'company', None)
            if user_company:
                request.company = user_company

        # ── If user is logged in, enforce membership isolation ─────────────────
        if (
            request.user.is_authenticated
            and not request.user.is_superuser
            and request.company is not None
        ):
            user_company = getattr(request.user, 'company', None)
            if user_company and user_company.id != request.company.id:
                if request.path_info.startswith('/api/'):
                    from django.http import JsonResponse
                    return JsonResponse({'detail': "You do not have access to this company's portal."}, status=403)
                return HttpResponseForbidden("You do not have access to this company's portal.")

        return self.get_response(request)

    def _extract_slug(self, path: str) -> str | None:
        """
        Extract company slug from /<slug>/... URLs.
        Returns None if the first path segment doesn't look like a company slug.
        """
        # path starts with '/', so split gives ['', 'slug', 'rest', ...]
        parts = path.strip('/').split('/')
        if not parts or not parts[0]:
            return None
        candidate = parts[0]
        # Quick sanity: slugs are lowercase alphanumeric + hyphens, 2-60 chars
        if len(candidate) < 2 or len(candidate) > 60:
            return None
        if not all(c.isalnum() or c == '-' for c in candidate):
            return None
        return candidate

    def _resolve_company(self, slug: str):
        """Lookup company by slug; returns None if not found."""
        try:
            return Company.objects.select_related('plan', 'settings').get(slug=slug)
        except Company.DoesNotExist:
            return None
