from typing import Any, Callable, Dict, Generic, Optional, Type, TypeVar, Union
import logging
import inspect

from core.database import get_core_db
from core.security import get_current_user, is_hub_admin, resolve_tenant_id_required, validate_access_to_resource, get_active_system
from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query, Request

from api.v1.common.catalog_validation_errors import CatalogValidationError
from pydantic import BaseModel
from sqlalchemy.orm import Session

logger = logging.getLogger(__name__)

# Type variables for generic types
ModelType = TypeVar("ModelType")
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)
ResponseSchemaType = TypeVar("ResponseSchemaType", bound=BaseModel)
ServiceType = TypeVar("ServiceType")


class TenantCRUDRoutes(
    Generic[CreateSchemaType, UpdateSchemaType, ResponseSchemaType, ServiceType]
):
    """
    Generic CRUD routes factory for tenant-scoped resources

    Supports both parent resources (with list/pagination) and child resources (nested under parent).

    Usage examples:

    1. Parent resource with list (e.g., /pedimentos):
        router = TenantCRUDRoutes(
            service=PedimentosService,
            create_schema=PedimentosCreate,
            update_schema=PedimentosUpdate,
            response_schema=PedimentosResponse,
            prefix="/pedimentos",
            tags=["Pedimentos"],
            resource_name="Pedimento",
            id_name="pedimento_id",
            enable_list=True,
        ).router

    2. Child resource (e.g., /pedimentos/{pedimento_id}/config-additional):
        router = TenantCRUDRoutes(
            service=PedimentoConfigAdditionalService,
            create_schema=PedimentoConfigAdditionalCreate,
            update_schema=PedimentoConfigAdditionalUpdate,
            response_schema=PedimentoConfigAdditionalResponse,
            prefix="/{pedimento_id}/config-additional",
            tags=["Pedimento Config Additional"],
            resource_name="Config additional",
            parent_id_name="pedimento_id",
            enable_list=False,
        ).router

    3. Parent resource with string ID (e.g., /vehicles with vehicle_key):
        router = TenantCRUDRoutes(
            service=VehicleService,
            create_schema=VehicleCreate,
            update_schema=VehicleUpdate,
            response_schema=VehicleResponse,
            prefix="/vehicles",
            tags=["Vehicles"],
            resource_name="Vehicle",
            id_name="vehicle_key",
            id_type=str,  # Specify string type for vehicle_key
            enable_list=True,
        ).router
    """

    def __init__(
        self,
        service: Type[ServiceType],
        create_schema: Type[CreateSchemaType],
        update_schema: Type[UpdateSchemaType],
        response_schema: Type[ResponseSchemaType],
        prefix: str,
        tags: list[str],
        resource_name: str = "Resource",
        # For parent resources (e.g., "pedimento_id")
        id_name: Optional[str] = None,
        id_type: Type = int,  # Type of the ID (int, str, etc.)
        parent_id_name: Optional[
            str
        ] = None,  # For child resources (e.g., "pedimento_id")
        db_dependency: Callable = get_core_db,
        auth_dependency: Callable = get_current_user,
        validate_parent_match: bool = True,  # Validate parent_id matches in create
        enable_list: bool = False,  # Enable GET list endpoint with pagination
        enable_filters: bool = False,  # Enable custom filters in list endpoint
        default_page_size: int = 50,
        max_page_size: int = 2000,
        # Permissions for each operation
        list_permissions: Optional[list[str]] = None,
        get_permissions: Optional[list[str]] = None,
        create_permissions: Optional[list[str]] = None,
        update_permissions: Optional[list[str]] = None,
        delete_permissions: Optional[list[str]] = None,
        require_all: bool = True,  # If True, requires ALL permissions; if False, requires ANY
    ):
        self.service = service
        self.create_schema = create_schema
        self.update_schema = update_schema
        self.response_schema = response_schema
        self.resource_name = resource_name
        self.id_name = id_name or parent_id_name or "id"
        self.id_type = id_type
        self.parent_id_name = parent_id_name
        self.db_dependency = db_dependency
        self.auth_dependency = auth_dependency
        self.validate_parent_match = validate_parent_match
        self.enable_list = enable_list
        self.enable_filters = enable_filters
        self.default_page_size = default_page_size
        self.max_page_size = max_page_size
        self.list_permissions = list_permissions
        self.get_permissions = get_permissions
        self.create_permissions = create_permissions
        self.update_permissions = update_permissions
        self.delete_permissions = delete_permissions
        self.require_all = require_all

        self.router = APIRouter(prefix=prefix, tags=tags)
        self._register_routes()

    def _register_routes(self):
        """Register all CRUD routes"""

        # LIST route (optional, for parent resources)
        if self.enable_list:
            if self.enable_filters:

                @self.router.get(
                    "/",
                    response_model=Dict[str, Any],
                    summary=f"List {self.resource_name}s",
                    description=f"Get paginated list of {self.resource_name}s with optional filters",
                )
                async def list_resources(
                    request: Request,
                    company_id: int = Query(..., description="Company ID"),
                    all_companies: bool = Query(False, description="Whether to search in all companies of the tenant"),
                    page: int = Query(1, ge=1, description="Page number"),
                    page_size: int = Query(
                        self.default_page_size,
                        ge=1,
                        le=self.max_page_size,
                        description="Page size",
                    ),
                    sort_by: Optional[str] = Query(None, description="Column to sort by"),
                    sort_order: Optional[str] = Query("asc", pattern="^(asc|desc)$", description="Sort order (asc or desc)"),
                    db: Session = Depends(self.db_dependency),
                    current_user: Dict[str, Any] = Depends(self.auth_dependency),
                ):
                    if all_companies:
                        # Hub admin: tenant_id=None → el servicio devuelve todas las empresas
                        tenant_id = resolve_tenant_id_required(current_user, db=db)
                        target_company_id = None
                    else:
                        tenant_id = validate_access_to_resource(
                            db,
                            company_id,
                            current_user,
                            self.list_permissions,
                            self.require_all,
                        )
                        target_company_id = company_id

                    skip = (page - 1) * page_size

                    # Extraer todos los parámetros de búsqueda dinámicamente
                    # Excluimos los parámetros estándar de paginación y control
                    standard_params = {"company_id", "all_companies", "page", "page_size", "sort_by", "sort_order"}
                    filters = {
                        k: v
                        for k, v in request.query_params.items()
                        if k not in standard_params and v is not None and v != ""
                    }

                    # Inyectar active_system (header/cookie) si no viene por query param
                    active_system = get_active_system(request)
                    if active_system and "system" not in filters:
                        filters["system"] = active_system

                    # Determine what parameters the service method accepts
                    sig = inspect.signature(self.service.get_all)
                    kwargs = {}
                    if "sort_by" in sig.parameters:
                        kwargs["sort_by"] = sort_by
                    if "sort_order" in sig.parameters:
                        kwargs["sort_order"] = sort_order

                    try:
                        items, total = self.service.get_all(
                            db, tenant_id, target_company_id, skip, page_size, filters, **kwargs
                        )
                    except Exception as e:
                        logger.error(f"Error in {self.resource_name} list service: {str(e)}", exc_info=True)
                        raise HTTPException(
                            status_code=500,
                            detail=f"Error listing {self.resource_name}s: {str(e)}"
                        )

                    try:
                        return {
                            "items": [
                                self.response_schema.model_validate(item) for item in items
                            ],
                            "total": total,
                            "page": page,
                            "page_size": page_size,
                        }
                    except Exception as e:
                        logger.error(f"Error validating {self.resource_name} response schema: {str(e)}", exc_info=True)
                        raise HTTPException(
                            status_code=500,
                            detail=f"Data validation error in {self.resource_name}"
                        )

            else:

                @self.router.get(
                    "/",
                    response_model=Dict[str, Any],
                    summary=f"List {self.resource_name}s",
                    description=f"Get paginated list of {self.resource_name}s",
                )
                async def list_resources(
                    company_id: int = Query(..., description="Company ID"),
                    all_companies: bool = Query(False, description="Whether to search in all companies of the tenant"),
                    page: int = Query(1, ge=1, description="Page number"),
                    page_size: int = Query(
                        self.default_page_size,
                        ge=1,
                        le=self.max_page_size,
                        description="Page size",
                    ),
                    sort_by: Optional[str] = Query(None, description="Column to sort by"),
                    sort_order: Optional[str] = Query("asc", pattern="^(asc|desc)$", description="Sort order (asc or desc)"),
                    db: Session = Depends(self.db_dependency),
                    current_user: Dict[str, Any] = Depends(self.auth_dependency),
                ):
                    if all_companies:
                        # Hub admin: tenant_id=None → el servicio devuelve todas las empresas
                        tenant_id = resolve_tenant_id_required(current_user, db=db)
                        target_company_id = None
                    else:
                        tenant_id = validate_access_to_resource(
                            db,
                            company_id,
                            current_user,
                            self.list_permissions,
                            self.require_all,
                        )
                        target_company_id = company_id

                    skip = (page - 1) * page_size

                    # Determine what parameters the service method accepts
                    sig = inspect.signature(self.service.get_all)
                    kwargs = {}
                    if "sort_by" in sig.parameters:
                        kwargs["sort_by"] = sort_by
                    if "sort_order" in sig.parameters:
                        kwargs["sort_order"] = sort_order

                    try:
                        items, total = self.service.get_all(
                            db, tenant_id, target_company_id, skip, page_size, None, **kwargs
                        )
                    except Exception as e:
                        logger.error(f"Error in {self.resource_name} list service: {str(e)}", exc_info=True)
                        raise HTTPException(
                            status_code=500,
                            detail=f"Error listing {self.resource_name}s: {str(e)}"
                        )

                    try:
                        return {
                            "items": [
                                self.response_schema.model_validate(item) for item in items
                            ],
                            "total": total,
                            "page": page,
                            "page_size": page_size,
                        }
                    except Exception as e:
                        logger.error(f"Error validating {self.resource_name} response schema: {str(e)}", exc_info=True)
                        raise HTTPException(
                            status_code=500,
                            detail=f"Data validation error in {self.resource_name}"
                        )

        # GET single resource route
        # For parent resources: GET /{id}
        # For child resources: GET / (parent_id comes from path)
        if self.parent_id_name:
            # Child resource - single GET without ID in path
            @self.router.get(
                "/",
                response_model=self.response_schema,
                summary=f"Get {self.resource_name}",
                description=f"Get {self.resource_name} by {self.parent_id_name}",
            )
            async def get_resource(
                company_id: int = Query(..., description="Company ID"),
                db: Session = Depends(self.db_dependency),
                current_user: Dict[str, Any] = Depends(self.auth_dependency),
                **path_params,
            ):

                tenant_id = validate_access_to_resource(
                    db, company_id, current_user, self.get_permissions, self.require_all
                )
                parent_id = path_params.get(self.parent_id_name)

                # Try method with 4 params (pedimento_id, tenant_id, company_id)
                if hasattr(self.service, "get_by_pedimento_id"):
                    resource = self.service.get_by_pedimento_id(
                        db, parent_id, tenant_id, company_id
                    )
                # Fallback to method with 3 params
                elif hasattr(self.service, "get_by_id"):
                    resource = self.service.get_by_id(
                        db, parent_id, tenant_id, company_id
                    )
                else:
                    resource = self.service.get(db, parent_id, tenant_id, company_id)

                if not resource:
                    raise HTTPException(
                        status_code=404, detail=f"{self.resource_name} not found"
                    )
                return resource

        else:
            # Parent resource - GET by ID in path
            @self.router.get(
                f"/{{{self.id_name}}}",
                response_model=self.response_schema,
                summary=f"Get {self.resource_name} by ID",
                description=f"Get a specific {self.resource_name} by {self.id_name}",
            )
            async def get_resource_by_id(
                resource_id: Union[int, str] = Path(
                    ..., alias=self.id_name, description=f"{self.resource_name} ID"
                ),
                company_id: int = Query(..., description="Company ID"),
                db: Session = Depends(self.db_dependency),
                current_user: Dict[str, Any] = Depends(self.auth_dependency),
            ):
                tenant_id = validate_access_to_resource(
                    db, company_id, current_user, self.get_permissions, self.require_all
                )

                try:
                    resource = self.service.get_by_id(
                        db, resource_id, tenant_id, company_id
                    )
                except Exception as e:
                    logger.error(f"Error in {self.resource_name} get service: {str(e)}", exc_info=True)
                    raise HTTPException(
                        status_code=500,
                        detail=f"Error retrieving {self.resource_name}: {str(e)}"
                    )

                if not resource:
                    raise HTTPException(
                        status_code=404, detail=f"{self.resource_name} not found"
                    )
                return resource

        # POST route
        if self.parent_id_name:
            # Child resource - needs parent_id from path

            # Create a closure to capture the schema type
            create_schema = self.create_schema

            @self.router.post(
                "/",
                response_model=self.response_schema,
                status_code=201,
                summary=f"Create {self.resource_name}",
                description=f"Create a new {self.resource_name}",
            )
            async def create_child_resource(
                request: Request,
                company_id: int = Query(..., description="Company ID"),
                data: create_schema = Body(...),  # type: ignore
                db: Session = Depends(self.db_dependency),
                current_user: Dict[str, Any] = Depends(self.auth_dependency),
            ):
                tenant_id = validate_access_to_resource(
                    db,
                    company_id,
                    current_user,
                    self.create_permissions,
                    self.require_all,
                )

                # Inyectar sistema activo en el campo system si el recurso lo soporta
                if self.enable_filters:
                    active_system = get_active_system(request)
                    if active_system and hasattr(data, "system"):
                        data = data.model_copy(update={"system": active_system})

                # For child resources, parent_id validation would go here
                try:
                    resource = self.service.create(db, data, tenant_id, company_id)
                    return resource
                except CatalogValidationError as e:
                    raise HTTPException(
                        status_code=422,
                        detail={
                            "message": str(e),
                            "errors": e.errors,
                        },
                    )
                except ValueError as e:
                    # Capturar errores de validación (como duplicados)
                    raise HTTPException(status_code=400, detail=str(e))
                except Exception as e:
                    # Re-lanzar otros errores
                    raise

        else:
            # Parent resource - no parent_id needed

            # Create a closure to capture the schema type
            create_schema = self.create_schema

            @self.router.post(
                "/",
                response_model=self.response_schema,
                status_code=201,
                summary=f"Create {self.resource_name}",
                description=f"Create a new {self.resource_name}",
            )
            async def create_parent_resource(
                request: Request,
                company_id: int = Query(..., description="Company ID"),
                data: create_schema = Body(...),  # type: ignore
                db: Session = Depends(self.db_dependency),
                current_user: Dict[str, Any] = Depends(self.auth_dependency),
            ):
                tenant_id = validate_access_to_resource(
                    db,
                    company_id,
                    current_user,
                    self.create_permissions,
                    self.require_all,
                )

                # Inyectar sistema activo en el campo system si el recurso lo soporta
                if self.enable_filters:
                    active_system = get_active_system(request)
                    if active_system and hasattr(data, "system"):
                        data = data.model_copy(update={"system": active_system})

                try:
                    resource = self.service.create(db, data, tenant_id, company_id)
                    return resource
                except CatalogValidationError as e:
                    raise HTTPException(
                        status_code=422,
                        detail={
                            "message": str(e),
                            "errors": e.errors,
                        },
                    )
                except ValueError as e:
                    # Capturar errores de validación (como duplicados)
                    raise HTTPException(status_code=400, detail=str(e))
                except Exception as e:
                    # Re-lanzar otros errores
                    raise

        # PUT route
        # For parent resources: PUT /{id}
        # For child resources: PUT / (parent_id comes from path)
        if self.parent_id_name:
            # Child resource

            # Create a closure to capture the schema type
            update_schema = self.update_schema

            @self.router.put(
                "/",
                response_model=self.response_schema,
                summary=f"Update {self.resource_name}",
                description=f"Update an existing {self.resource_name}",
            )
            async def update_resource(
                company_id: int = Query(..., description="Company ID"),
                data: update_schema = Body(...),  # type: ignore
                db: Session = Depends(self.db_dependency),
                current_user: Dict[str, Any] = Depends(self.auth_dependency),
                **path_params,
            ):
                tenant_id = validate_access_to_resource(
                    db,
                    company_id,
                    current_user,
                    self.update_permissions,
                    self.require_all,
                )
                parent_id = path_params.get(self.parent_id_name)

                try:
                    resource = self.service.update(
                        db, parent_id, tenant_id, data, company_id
                    )
                except CatalogValidationError as e:
                    raise HTTPException(
                        status_code=422,
                        detail={
                            "message": str(e),
                            "errors": e.errors,
                        },
                    )
                except ValueError as e:
                    # Capturar errores de validación (como duplicados)
                    raise HTTPException(status_code=400, detail=str(e))

                if not resource:
                    raise HTTPException(
                        status_code=404, detail=f"{self.resource_name} not found"
                    )
                return resource

        else:
            # Parent resource

            # Create a closure to capture the schema type
            update_schema = self.update_schema

            @self.router.put(
                f"/{{{self.id_name}}}/",
                response_model=self.response_schema,
                summary=f"Update {self.resource_name}",
                description=f"Update an existing {self.resource_name} by {self.id_name}",
            )
            async def update_resource_by_id(
                resource_id: Union[int, str] = Path(
                    ..., alias=self.id_name, description=f"{self.resource_name} ID"
                ),
                company_id: int = Query(..., description="Company ID"),
                data: update_schema = Body(...),  # type: ignore
                db: Session = Depends(self.db_dependency),
                current_user: Dict[str, Any] = Depends(self.auth_dependency),
            ):
                f"""Update {self.resource_name}"""
                tenant_id = validate_access_to_resource(
                    db,
                    company_id,
                    current_user,
                    self.update_permissions,
                    self.require_all,
                )

                try:
                    resource = self.service.update(
                        db, resource_id, tenant_id, data, company_id
                    )
                except CatalogValidationError as e:
                    raise HTTPException(
                        status_code=422,
                        detail={
                            "message": str(e),
                            "errors": e.errors,
                        },
                    )
                except ValueError as e:
                    # Capturar errores de validación (como duplicados)
                    raise HTTPException(status_code=400, detail=str(e))

                if not resource:
                    raise HTTPException(
                        status_code=404, detail=f"{self.resource_name} not found"
                    )
                return resource

        # DELETE route
        # For parent resources: DELETE /{id}
        # For child resources: DELETE / (parent_id comes from path)
        if self.parent_id_name:
            # Child resource
            @self.router.delete(
                "/",
                status_code=204,
                summary=f"Delete {self.resource_name}",
                description=f"Delete an existing {self.resource_name}",
            )
            async def delete_resource(
                company_id: int = Query(..., description="Company ID"),
                db: Session = Depends(self.db_dependency),
                current_user: Dict[str, Any] = Depends(self.auth_dependency),
                **path_params,
            ):
                tenant_id = validate_access_to_resource(
                    db,
                    company_id,
                    current_user,
                    self.delete_permissions,
                    self.require_all,
                )
                parent_id = path_params.get(self.parent_id_name)

                success = self.service.delete(db, parent_id, tenant_id, company_id)

                if not success:
                    raise HTTPException(
                        status_code=404, detail=f"{self.resource_name} not found"
                    )
                return None

        else:
            # Parent resource
            @self.router.delete(
                f"/{{{self.id_name}}}",
                status_code=204,
                summary=f"Delete {self.resource_name}",
                description=f"Delete an existing {self.resource_name} by {self.id_name}",
            )
            async def delete_resource_by_id(
                resource_id: Union[int, str] = Path(
                    ..., alias=self.id_name, description=f"{self.resource_name} ID"
                ),
                company_id: int = Query(..., description="Company ID"),
                db: Session = Depends(self.db_dependency),
                current_user: Dict[str, Any] = Depends(self.auth_dependency),
            ):
                tenant_id = validate_access_to_resource(
                    db,
                    company_id,
                    current_user,
                    self.delete_permissions,
                    self.require_all,
                )

                success = self.service.delete(db, resource_id, tenant_id, company_id)

                if not success:
                    raise HTTPException(
                        status_code=404, detail=f"{self.resource_name} not found"
                    )
                return None
