"""
Cross-reference validator for YAML schema files.

Validates that all endpoints, domains, entry_points, and references
are consistent across the entire schema definition set.

Usage:
    from lightwave.schema.validation.cross_validator import CrossReferenceValidator

    validator = CrossReferenceValidator()
    report = validator.validate_all()

    if not report.is_valid:
        for issue in report.issues:
            print(f"{issue.file}:{issue.location} - {issue.message}")
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import Any

import yaml

from lightwave.schema.core.types import (
    CrossReferenceReport,
    EndpointURL,
    EntryPoint,
    PlatformRegistry,
)


class CrossReferenceValidator:
    """
    Validates cross-references between all YAML schema files.

    This ensures the "Single Source of Truth" is actually consistent:
    - All endpoints reference valid domains
    - All entry_points match defined routes/domains
    - All tenant references are valid
    - All user_type references are valid
    - All layout references are valid
    """

    def __init__(self, schema_dir: Path | None = None):
        self.schema_dir = schema_dir or Path(__file__).parent / "definitions"
        self._schemas: dict[str, dict[str, Any]] = {}
        self._registry: PlatformRegistry | None = None

    def _load_schema(self, name: str) -> dict[str, Any]:
        """Load a schema file by name."""
        if name not in self._schemas:
            file_path = self.schema_dir / f"{name}.yaml"
            if file_path.exists():
                with open(file_path) as f:
                    self._schemas[name] = yaml.safe_load(f) or {}
            else:
                self._schemas[name] = {}
        return self._schemas[name]

    def _build_registry(self) -> PlatformRegistry:
        """Build the platform registry from domains.yaml, routes.yaml, rbac.yaml, layouts.yaml."""
        if self._registry:
            return self._registry

        # Load source files
        domains = self._load_schema("domains")
        routes = self._load_schema("routes")
        rbac = self._load_schema("rbac")
        layouts = self._load_schema("layouts")

        # Extract tenants
        tenants = set()
        tenant_domains = set()
        for tenant_slug, tenant_data in domains.get("tenants", {}).items():
            tenants.add(tenant_slug)
            for domain in tenant_data.get("domains", {}).values():
                tenant_domains.add(domain)

        # Extract backend service domains from domains.yaml ONLY - do not hardcode!
        # The SST (domains.yaml) is the authoritative source for valid backend domains.
        backend_domains = set()
        for service_data in domains.get("backend_services", {}).values():
            domain = service_data.get("domain", "")
            # Clean up domain patterns like "api.lightwave-media.ltd/{domain}"
            base_domain = domain.split("/")[0].split("{")[0].strip()
            if base_domain:
                backend_domains.add(base_domain)
        # Also add the root domain (parent of all backend services)
        if backend_domains:
            # Extract root from first backend domain (e.g., "lightwave-media.ltd" from "api.lightwave-media.ltd")
            sample_domain = next(iter(backend_domains))
            parts = sample_domain.split(".")
            if len(parts) >= 2:
                root_domain = ".".join(parts[-2:])  # e.g., "lightwave-media.ltd"
                backend_domains.add(root_domain)

        # Extract webhook paths from domains.yaml ONLY - do not hardcode!
        # The SST (domains.yaml) is the authoritative source for valid webhook paths.
        webhook_paths = set()
        for webhook_name, webhook_data in domains.get("webhooks", {}).items():
            path = webhook_data.get("path", webhook_name)
            webhook_paths.add(path)

        # Extract route patterns
        route_patterns = set()
        for _category, category_routes in routes.get("routes", {}).items():
            if isinstance(category_routes, list):
                for route in category_routes:
                    if "pattern" in route:
                        route_patterns.add(route["pattern"])
            elif isinstance(category_routes, dict) and "routes" in category_routes:
                for route in category_routes["routes"]:
                    if "pattern" in route:
                        route_patterns.add(route["pattern"])

        # Extract user types from rbac.yaml ONLY - do not hardcode!
        # The SST (rbac.yaml) is the authoritative source for valid user types.
        user_types = set()
        for role_name in rbac.get("user_roles", {}).keys():
            user_types.add(role_name)
        # Also check user_types alias if present
        for role_name in rbac.get("user_types", {}).keys():
            user_types.add(role_name)

        # Extract layouts from layouts.yaml ONLY - do not hardcode!
        # The SST (layouts.yaml) is the authoritative source for valid layouts.
        layout_names = set()
        for layout_name in layouts.get("layouts", {}).keys():
            layout_names.add(layout_name)

        # Extract feature flags from domains.yaml tenants
        feature_flags = set()
        for tenant_data in domains.get("tenants", {}).values():
            for flag in tenant_data.get("features", {}).keys():
                feature_flags.add(flag)

        self._registry = PlatformRegistry(
            tenants=frozenset(tenants),
            tenant_domains=frozenset(tenant_domains),
            backend_domains=frozenset(backend_domains),
            webhook_paths=frozenset(webhook_paths),
            route_patterns=frozenset(route_patterns),
            user_types=frozenset(user_types),
            layouts=frozenset(layout_names),
            feature_flags=frozenset(feature_flags),
        )
        return self._registry

    def validate_all(self) -> CrossReferenceReport:
        """Validate all schema files for cross-reference consistency."""
        report = CrossReferenceReport()
        registry = self._build_registry()

        # Track stats
        stats = {
            "files_checked": 0,
            "endpoints_checked": 0,
            "entry_points_checked": 0,
            "domains_checked": 0,
            "user_types_checked": 0,
            "layouts_checked": 0,
        }

        # Validate each schema file
        validators = [
            ("email", self._validate_email),
            ("user_flows", self._validate_user_flows),
            ("routes", self._validate_routes),
            ("pages", self._validate_pages),
            ("navigation", self._validate_navigation),
            ("features", self._validate_features),
            ("domains", self._validate_domains),
        ]

        for schema_name, validator_func in validators:
            data = self._load_schema(schema_name)
            if data:
                stats["files_checked"] += 1
                file_stats = validator_func(data, registry, report)
                for key, value in file_stats.items():
                    stats[key] = stats.get(key, 0) + value

        report.stats = stats
        return report

    def _validate_email(
        self,
        data: dict[str, Any],
        registry: PlatformRegistry,
        report: CrossReferenceReport,
    ) -> dict[str, int]:
        """Validate email.yaml webhook endpoints and routing."""
        stats = {"endpoints_checked": 0, "domains_checked": 0}

        # Validate webhook_events endpoints
        for event_name, event_data in data.get("webhook_events", {}).items():
            endpoint = event_data.get("endpoint", "")
            if endpoint:
                stats["endpoints_checked"] += 1
                self._validate_endpoint(
                    endpoint,
                    f"webhook_events.{event_name}.endpoint",
                    "email",
                    registry,
                    report,
                )

        # Validate outbound_addresses domains
        for domain, _addresses in data.get("outbound_addresses", {}).items():
            stats["domains_checked"] += 1
            if not registry.is_valid_domain(domain) and domain != "lightwave-media.ltd":
                # Check if it's a tenant domain
                if domain not in registry.tenant_domains and domain not in registry.backend_domains:
                    report.add_warning(
                        file="email.yaml",
                        location=f"outbound_addresses.{domain}",
                        issue_type="invalid_domain",
                        message=f"Domain '{domain}' not found in domains.yaml",
                        value=domain,
                        suggestion=f"Add '{domain}' to domains.yaml tenants or verify spelling",
                    )

        # Validate routing_rules from_address domains have {domain} placeholder
        for rule_name, rule_data in data.get("routing_rules", {}).items():
            from_addr = rule_data.get("from_address", "")
            if from_addr and "{domain}" not in from_addr:
                # It's a hardcoded domain - validate it
                domain_match = re.search(r"@([\w\-\.]+)", from_addr)
                if domain_match:
                    domain = domain_match.group(1)
                    stats["domains_checked"] += 1
                    if not registry.is_valid_domain(domain):
                        report.add_warning(
                            file="email.yaml",
                            location=f"routing_rules.{rule_name}.from_address",
                            issue_type="invalid_domain",
                            message=f"Hardcoded domain '{domain}' not in domains.yaml",
                            value=from_addr,
                            suggestion=f"Use '{{domain}}' placeholder or add '{domain}' to domains.yaml",
                        )

        return stats

    def _validate_user_flows(
        self,
        data: dict[str, Any],
        registry: PlatformRegistry,
        report: CrossReferenceReport,
    ) -> dict[str, int]:
        """Validate user_flows.yaml entry_points and user types."""
        stats = {"entry_points_checked": 0, "user_types_checked": 0, "layouts_checked": 0}

        # Validate platform_urls backend services
        for service_name, service_data in data.get("platform_urls", {}).get("backend_services", {}).items():
            base_url = service_data.get("base", "")
            if base_url:
                stats["entry_points_checked"] += 1
                self._validate_entry_point(
                    base_url,
                    f"platform_urls.backend_services.{service_name}.base",
                    "user_flows",
                    registry,
                    report,
                )

            # Validate routes within service
            for route_pattern, route_data in service_data.get("routes", {}).items():
                if isinstance(route_data, dict):
                    user_type = route_data.get("user_type")
                    if user_type:
                        stats["user_types_checked"] += 1
                        if not registry.is_valid_user_type(user_type):
                            report.add_issue(
                                file="user_flows.yaml",
                                location=f"platform_urls.backend_services.{service_name}.routes.{route_pattern}.user_type",
                                issue_type="invalid_user_type",
                                message=f"User type '{user_type}' not defined in rbac.yaml",
                                value=user_type,
                                suggestion=f"Valid user types: {', '.join(sorted(registry.user_types)[:5])}...",
                            )

        # Validate tenant entry points
        for tenant_slug, tenant_data in data.get("platform_urls", {}).get("tenants", {}).items():
            stats["entry_points_checked"] += 1
            if not registry.is_valid_tenant(tenant_slug):
                report.add_issue(
                    file="user_flows.yaml",
                    location=f"platform_urls.tenants.{tenant_slug}",
                    issue_type="invalid_tenant",
                    message=f"Tenant '{tenant_slug}' not defined in domains.yaml",
                    value=tenant_slug,
                    suggestion=f"Valid tenants: {', '.join(sorted(registry.tenants))}",
                )

            # Validate domain entries (domain names are the keys)
            for domain_name, domain_config in tenant_data.get("domains", {}).items():
                stats["entry_points_checked"] += 1
                self._validate_domain(
                    domain_name,
                    f"platform_urls.tenants.{tenant_slug}.domains.{domain_name}",
                    "user_flows",
                    registry,
                    report,
                )
                # Also validate layout if present
                if isinstance(domain_config, dict):
                    layout = domain_config.get("layout")
                    if layout:
                        stats["layouts_checked"] += 1
                        if not registry.is_valid_layout(layout):
                            report.add_issue(
                                file="user_flows.yaml",
                                location=f"platform_urls.tenants.{tenant_slug}.domains.{domain_name}.layout",
                                issue_type="invalid_layout",
                                message=f"Layout '{layout}' not defined in layouts.yaml",
                                value=layout,
                                suggestion=f"Valid layouts: {', '.join(sorted(registry.layouts))}",
                            )

        # Validate user_flows entry_points and steps
        for flow_id, flow_data in data.get("user_flows", {}).items():
            # Entry points
            for i, entry_point in enumerate(flow_data.get("entry_points", [])):
                stats["entry_points_checked"] += 1
                self._validate_entry_point(
                    entry_point,
                    f"user_flows.{flow_id}.entry_points[{i}]",
                    "user_flows",
                    registry,
                    report,
                )

            # Steps
            for i, step in enumerate(flow_data.get("steps", [])):
                # Validate layout
                layout = step.get("layout")
                if layout:
                    stats["layouts_checked"] += 1
                    if not registry.is_valid_layout(layout):
                        report.add_issue(
                            file="user_flows.yaml",
                            location=f"user_flows.{flow_id}.steps[{i}].layout",
                            issue_type="invalid_layout",
                            message=f"Layout '{layout}' not defined in layouts.yaml",
                            value=layout,
                            suggestion=f"Valid layouts: {', '.join(sorted(registry.layouts))}",
                        )

                # Validate user_type
                user_type = step.get("user_type")
                if user_type:
                    stats["user_types_checked"] += 1
                    if not registry.is_valid_user_type(user_type):
                        report.add_issue(
                            file="user_flows.yaml",
                            location=f"user_flows.{flow_id}.steps[{i}].user_type",
                            issue_type="invalid_user_type",
                            message=f"User type '{user_type}' not defined in rbac.yaml",
                            value=user_type,
                            suggestion=f"Valid user types: {', '.join(sorted(registry.user_types)[:5])}...",
                        )

            # Validate requires_user_type
            requires = flow_data.get("requires_user_type")
            if requires:
                stats["user_types_checked"] += 1
                if not registry.is_valid_user_type(requires):
                    report.add_issue(
                        file="user_flows.yaml",
                        location=f"user_flows.{flow_id}.requires_user_type",
                        issue_type="invalid_user_type",
                        message=f"User type '{requires}' not defined in rbac.yaml",
                        value=requires,
                        suggestion=f"Valid user types: {', '.join(sorted(registry.user_types)[:5])}...",
                    )

        return stats

    def _validate_routes(
        self,
        data: dict[str, Any],
        registry: PlatformRegistry,
        report: CrossReferenceReport,
    ) -> dict[str, int]:
        """Validate routes.yaml layouts and user types."""
        stats = {"user_types_checked": 0, "layouts_checked": 0}

        for category, category_routes in data.get("routes", {}).items():
            routes_list = []
            if isinstance(category_routes, list):
                routes_list = category_routes
            elif isinstance(category_routes, dict) and "routes" in category_routes:
                routes_list = category_routes["routes"]
                # Validate tenant reference
                tenant = category_routes.get("tenant")
                if tenant and not registry.is_valid_tenant(tenant):
                    report.add_issue(
                        file="routes.yaml",
                        location=f"routes.{category}.tenant",
                        issue_type="invalid_tenant",
                        message=f"Tenant '{tenant}' not defined in domains.yaml",
                        value=tenant,
                        suggestion=f"Valid tenants: {', '.join(sorted(registry.tenants))}",
                    )

            for i, route in enumerate(routes_list):
                # Validate layout
                layout = route.get("layout")
                if layout:
                    stats["layouts_checked"] += 1
                    if not registry.is_valid_layout(layout):
                        report.add_issue(
                            file="routes.yaml",
                            location=f"routes.{category}[{i}].layout",
                            issue_type="invalid_layout",
                            message=f"Layout '{layout}' not defined in layouts.yaml",
                            value=layout,
                            suggestion=f"Valid layouts: {', '.join(sorted(registry.layouts))}",
                        )

                # Validate requires (user type)
                requires = route.get("requires")
                if requires:
                    stats["user_types_checked"] += 1
                    if not registry.is_valid_user_type(requires):
                        report.add_issue(
                            file="routes.yaml",
                            location=f"routes.{category}[{i}].requires",
                            issue_type="invalid_user_type",
                            message=f"User type '{requires}' not defined in rbac.yaml",
                            value=requires,
                            suggestion=f"Valid user types: {', '.join(sorted(registry.user_types)[:5])}...",
                        )

        return stats

    def _validate_pages(
        self,
        data: dict[str, Any],
        registry: PlatformRegistry,
        report: CrossReferenceReport,
    ) -> dict[str, int]:
        """Validate pages.yaml tenants, layouts, and domains."""
        stats = {"domains_checked": 0, "layouts_checked": 0}

        for tenant_slug, tenant_data in data.get("tenants", {}).items():
            # Validate tenant
            if not registry.is_valid_tenant(tenant_slug):
                report.add_issue(
                    file="pages.yaml",
                    location=f"tenants.{tenant_slug}",
                    issue_type="invalid_tenant",
                    message=f"Tenant '{tenant_slug}' not defined in domains.yaml",
                    value=tenant_slug,
                    suggestion=f"Valid tenants: {', '.join(sorted(registry.tenants))}",
                )

            # Handle two formats:
            # 1. Single site: { site: "domain.com", pages: [...] }
            # 2. Multi-site: { sites: { "domain1.com": { pages: [...] }, ... } }

            if "sites" in tenant_data:
                # Multi-site format
                for site_domain, site_data in tenant_data.get("sites", {}).items():
                    stats["domains_checked"] += 1
                    self._validate_domain(
                        site_domain,
                        f"tenants.{tenant_slug}.sites.{site_domain}",
                        "pages",
                        registry,
                        report,
                    )
                    # Validate page layouts for this site
                    for i, page in enumerate(site_data.get("pages", [])):
                        layout = page.get("layout")
                        if layout:
                            stats["layouts_checked"] += 1
                            if not registry.is_valid_layout(layout):
                                report.add_issue(
                                    file="pages.yaml",
                                    location=f"tenants.{tenant_slug}.sites.{site_domain}.pages[{i}].layout",
                                    issue_type="invalid_layout",
                                    message=f"Layout '{layout}' not defined in layouts.yaml",
                                    value=layout,
                                    suggestion=f"Valid layouts: {', '.join(sorted(registry.layouts))}",
                                )
            else:
                # Single site format
                site = tenant_data.get("site", "")
                if site:
                    stats["domains_checked"] += 1
                    self._validate_domain(
                        site,
                        f"tenants.{tenant_slug}.site",
                        "pages",
                        registry,
                        report,
                    )

                # Validate page layouts
                for i, page in enumerate(tenant_data.get("pages", [])):
                    layout = page.get("layout")
                    if layout:
                        stats["layouts_checked"] += 1
                        if not registry.is_valid_layout(layout):
                            report.add_issue(
                                file="pages.yaml",
                                location=f"tenants.{tenant_slug}.pages[{i}].layout",
                                issue_type="invalid_layout",
                                message=f"Layout '{layout}' not defined in layouts.yaml",
                                value=layout,
                                suggestion=f"Valid layouts: {', '.join(sorted(registry.layouts))}",
                            )

        return stats

    def _validate_navigation(
        self,
        data: dict[str, Any],
        registry: PlatformRegistry,
        report: CrossReferenceReport,
    ) -> dict[str, int]:
        """Validate navigation.yaml site references."""
        stats = {"domains_checked": 0}

        for site_domain in data.get("sites", {}).keys():
            stats["domains_checked"] += 1
            self._validate_domain(
                site_domain,
                f"sites.{site_domain}",
                "navigation",
                registry,
                report,
            )

        return stats

    def _validate_features(
        self,
        data: dict[str, Any],
        registry: PlatformRegistry,
        report: CrossReferenceReport,
    ) -> dict[str, int]:
        """Validate features.yaml tenant references."""
        stats = {}

        for tenant_slug in data.get("tenants", {}).keys():
            if not registry.is_valid_tenant(tenant_slug):
                report.add_issue(
                    file="features.yaml",
                    location=f"tenants.{tenant_slug}",
                    issue_type="invalid_tenant",
                    message=f"Tenant '{tenant_slug}' not defined in domains.yaml",
                    value=tenant_slug,
                    suggestion=f"Valid tenants: {', '.join(sorted(registry.tenants))}",
                )

        return stats

    def _validate_domains(
        self,
        data: dict[str, Any],
        registry: PlatformRegistry,
        report: CrossReferenceReport,
    ) -> dict[str, int]:
        """Validate domains.yaml internal consistency."""
        stats = {}
        # domains.yaml is the source of truth, so we just check internal consistency
        # Ensure all backend services have valid domain patterns
        for service_name, service_data in data.get("backend_services", {}).items():
            domain = service_data.get("domain", "")
            if domain and not domain.endswith(".lightwave-media.ltd") and "{" not in domain:
                report.add_warning(
                    file="domains.yaml",
                    location=f"backend_services.{service_name}.domain",
                    issue_type="invalid_domain",
                    message="Backend service domain should be *.lightwave-media.ltd",
                    value=domain,
                    suggestion=f"Use '{service_name}.lightwave-media.ltd' pattern",
                )

        return stats

    def _validate_endpoint(
        self,
        endpoint: str,
        location: str,
        file: str,
        registry: PlatformRegistry,
        report: CrossReferenceReport,
    ) -> None:
        """Validate a full endpoint URL."""
        try:
            ep = EndpointURL(url=endpoint)
            # Check domain is valid
            if not registry.is_valid_domain(ep.domain):
                report.add_issue(
                    file=f"{file}.yaml",
                    location=location,
                    issue_type="invalid_domain",
                    message=f"Endpoint domain '{ep.domain}' not defined in domains.yaml",
                    value=endpoint,
                    suggestion=f"Valid backend domains: {', '.join(sorted(registry.backend_domains)[:3])}...",
                )

            # Check webhook path if it's a webhook endpoint
            if "webhook" in ep.path.lower() or ep.domain == "webhooks.lightwave-media.ltd":
                path_parts = ep.path.strip("/").split("/")
                if len(path_parts) >= 2:
                    webhook_path = path_parts[1]  # e.g., "email" from "/webhooks/email/sent/"
                    if not registry.is_valid_webhook_path(webhook_path):
                        report.add_warning(
                            file=f"{file}.yaml",
                            location=location,
                            issue_type="invalid_webhook_path",
                            message=f"Webhook path '{webhook_path}' not defined in domains.yaml webhooks",
                            value=endpoint,
                            suggestion=f"Add '{webhook_path}' to domains.yaml webhooks section",
                        )
        except Exception as e:
            report.add_issue(
                file=f"{file}.yaml",
                location=location,
                issue_type="type_mismatch",
                message=f"Invalid endpoint URL format: {e}",
                value=endpoint,
            )

    def _validate_entry_point(
        self,
        entry_point: str | dict,
        location: str,
        file: str,
        registry: PlatformRegistry,
        report: CrossReferenceReport,
    ) -> None:
        """Validate an entry point pattern (string or dict format)."""
        try:
            # EntryPoint handles both string and dict formats
            ep = EntryPoint.model_validate(entry_point)

            # Check domain is valid
            if not registry.is_valid_domain(ep.domain):
                report.add_issue(
                    file=f"{file}.yaml",
                    location=location,
                    issue_type="invalid_domain",
                    message=f"Entry point domain '{ep.domain}' not defined in domains.yaml",
                    value=ep.value,
                    suggestion=f"Valid domains: {', '.join(sorted(list(registry.tenant_domains)[:3]))}...",
                )

            # If tenant is specified, validate it
            if ep.tenant and not registry.is_valid_tenant(ep.tenant):
                report.add_issue(
                    file=f"{file}.yaml",
                    location=f"{location}.tenant",
                    issue_type="invalid_tenant",
                    message=f"Entry point tenant '{ep.tenant}' not defined in domains.yaml",
                    value=ep.tenant,
                    suggestion=f"Valid tenants: {', '.join(sorted(registry.tenants))}",
                )
        except Exception as e:
            # Extract URL for display if it's a dict
            display_value = entry_point
            if isinstance(entry_point, dict):
                display_value = entry_point.get("url", str(entry_point))
            report.add_issue(
                file=f"{file}.yaml",
                location=location,
                issue_type="type_mismatch",
                message=f"Invalid entry point format: {e}",
                value=display_value,
            )

    def _validate_domain(
        self,
        domain: str,
        location: str,
        file: str,
        registry: PlatformRegistry,
        report: CrossReferenceReport,
    ) -> None:
        """Validate a domain reference."""
        if not registry.is_valid_domain(domain):
            report.add_warning(
                file=f"{file}.yaml",
                location=location,
                issue_type="invalid_domain",
                message=f"Domain '{domain}' not defined in domains.yaml",
                value=domain,
                suggestion=f"Add '{domain}' to domains.yaml tenants",
            )
