# [DEF:SettingsRouter:Module]
#
# @SEMANTICS: settings, api, router, fastapi
# @PURPOSE:   Provides API endpoints for managing application settings and Superset environments.
# @LAYER:     UI (API)
# @RELATION:  DEPENDS_ON -> ConfigManager
# @RELATION:  DEPENDS_ON -> ConfigModels
#
# @INVARIANT: All settings changes must be persisted via ConfigManager.
# @PUBLIC_API: router

# [SECTION: IMPORTS]
from fastapi import APIRouter, Depends, HTTPException
from typing import List
from pydantic import BaseModel
from ...core.config_models import AppConfig, Environment, GlobalSettings, LoggingConfig
from ...models.storage import StorageConfig
from ...dependencies import get_config_manager, has_permission
from ...core.config_manager import ConfigManager
from ...core.logger import logger, belief_scope
from ...core.superset_client import SupersetClient
from ...services.llm_prompt_templates import normalize_llm_settings
from ...models.llm import ValidationPolicy
from ...models.config import AppConfigRecord
from ...schemas.settings import ValidationPolicyCreate, ValidationPolicyUpdate, ValidationPolicyResponse
from ...core.database import get_db
from sqlalchemy.orm import Session
# [/SECTION]

# [DEF:LoggingConfigResponse:Class]
# @PURPOSE: Response model for logging configuration with current task log level.
# @SEMANTICS: logging, config, response
class LoggingConfigResponse(BaseModel):
    level: str
    task_log_level: str
    enable_belief_state: bool
# [/DEF:LoggingConfigResponse:Class]

router = APIRouter()


# [DEF:_normalize_superset_env_url:Function]
# @PURPOSE: Canonicalize Superset environment URL to base host/path without trailing /api/v1.
# @PRE: raw_url can be empty.
# @POST: Returns normalized base URL.
def _normalize_superset_env_url(raw_url: str) -> str:
    normalized = str(raw_url or "").strip().rstrip("/")
    if normalized.lower().endswith("/api/v1"):
        normalized = normalized[:-len("/api/v1")]
    return normalized.rstrip("/")
# [/DEF:_normalize_superset_env_url:Function]


# [DEF:_validate_superset_connection_fast:Function]
# @PURPOSE: Run lightweight Superset connectivity validation without full pagination scan.
# @PRE: env contains valid URL and credentials.
# @POST: Raises on auth/API failures; returns None on success.
def _validate_superset_connection_fast(env: Environment) -> None:
    client = SupersetClient(env)
    # 1) Explicit auth check
    client.authenticate()
    # 2) Single lightweight API call to ensure read access
    client.get_dashboards_page(
        query={
            "page": 0,
            "page_size": 1,
            "columns": ["id"],
        }
    )
# [/DEF:_validate_superset_connection_fast:Function]

# [DEF:get_settings:Function]
# @PURPOSE: Retrieves all application settings.
# @PRE:     Config manager is available.
# @POST:    Returns masked AppConfig.
# @RETURN: AppConfig - The current configuration.
@router.get("", response_model=AppConfig)
async def get_settings(
    config_manager: ConfigManager = Depends(get_config_manager),
    _ = Depends(has_permission("admin:settings", "READ"))
):
    with belief_scope("get_settings"):
        logger.info("[get_settings][Entry] Fetching all settings")
    config = config_manager.get_config().copy(deep=True)
    config.settings.llm = normalize_llm_settings(config.settings.llm)
    # Mask passwords
    for env in config.environments:
        if env.password:
            env.password = "********"
    return config
# [/DEF:get_settings:Function]

# [DEF:update_global_settings:Function]
# @PURPOSE: Updates global application settings.
# @PRE:     New settings are provided.
# @POST:    Global settings are updated.
# @PARAM: settings (GlobalSettings) - The new global settings.
# @RETURN: GlobalSettings - The updated settings.
@router.patch("/global", response_model=GlobalSettings)
async def update_global_settings(
    settings: GlobalSettings,
    config_manager: ConfigManager = Depends(get_config_manager),
    _ = Depends(has_permission("admin:settings", "WRITE"))
):
    with belief_scope("update_global_settings"):
        logger.info("[update_global_settings][Entry] Updating global settings")
    
    config_manager.update_global_settings(settings)
    return settings
# [/DEF:update_global_settings:Function]

# [DEF:get_storage_settings:Function]
# @PURPOSE: Retrieves storage-specific settings.
# @RETURN: StorageConfig - The storage configuration.
@router.get("/storage", response_model=StorageConfig)
async def get_storage_settings(
    config_manager: ConfigManager = Depends(get_config_manager),
    _ = Depends(has_permission("admin:settings", "READ"))
):
    with belief_scope("get_storage_settings"):
        return config_manager.get_config().settings.storage
# [/DEF:get_storage_settings:Function]

# [DEF:update_storage_settings:Function]
# @PURPOSE: Updates storage-specific settings.
# @PARAM: storage (StorageConfig) - The new storage settings.
# @POST: Storage settings are updated and saved.
# @RETURN: StorageConfig - The updated storage settings.
@router.put("/storage", response_model=StorageConfig)
async def update_storage_settings(
    storage: StorageConfig,
    config_manager: ConfigManager = Depends(get_config_manager),
    _ = Depends(has_permission("admin:settings", "WRITE"))
):
    with belief_scope("update_storage_settings"):
        is_valid, message = config_manager.validate_path(storage.root_path)
        if not is_valid:
            raise HTTPException(status_code=400, detail=message)
        
        settings = config_manager.get_config().settings
        settings.storage = storage
        config_manager.update_global_settings(settings)
        return config_manager.get_config().settings.storage
# [/DEF:update_storage_settings:Function]

# [DEF:get_environments:Function]
# @PURPOSE: Lists all configured Superset environments.
# @PRE:     Config manager is available.
# @POST:    Returns list of environments.
# @RETURN: List[Environment] - List of environments.
@router.get("/environments", response_model=List[Environment])
async def get_environments(
    config_manager: ConfigManager = Depends(get_config_manager),
    _ = Depends(has_permission("admin:settings", "READ"))
):
    with belief_scope("get_environments"):
        logger.info("[get_environments][Entry] Fetching environments")
    environments = config_manager.get_environments()
    return [
        env.copy(update={"url": _normalize_superset_env_url(env.url)})
        for env in environments
    ]
# [/DEF:get_environments:Function]

# [DEF:add_environment:Function]
# @PURPOSE: Adds a new Superset environment.
# @PRE:     Environment data is valid and reachable.
# @POST:    Environment is added to config.
# @PARAM: env (Environment) - The environment to add.
# @RETURN: Environment - The added environment.
@router.post("/environments", response_model=Environment)
async def add_environment(
    env: Environment,
    config_manager: ConfigManager = Depends(get_config_manager),
    _ = Depends(has_permission("admin:settings", "WRITE"))
):
    with belief_scope("add_environment"):
        logger.info(f"[add_environment][Entry] Adding environment {env.id}")
    env = env.copy(update={"url": _normalize_superset_env_url(env.url)})
    
    # Validate connection before adding (fast path)
    try:
        _validate_superset_connection_fast(env)
    except Exception as e:
        logger.error(f"[add_environment][Coherence:Failed] Connection validation failed: {e}")
        raise HTTPException(status_code=400, detail=f"Connection validation failed: {e}")

    config_manager.add_environment(env)
    return env
# [/DEF:add_environment:Function]

# [DEF:update_environment:Function]
# @PURPOSE: Updates an existing Superset environment.
# @PRE:     ID and valid environment data are provided.
# @POST:    Environment is updated in config.
# @PARAM: id (str) - The ID of the environment to update.
# @PARAM: env (Environment) - The updated environment data.
# @RETURN: Environment - The updated environment.
@router.put("/environments/{id}", response_model=Environment)
async def update_environment(
    id: str,
    env: Environment,
    config_manager: ConfigManager = Depends(get_config_manager)
):
    with belief_scope("update_environment"):
        logger.info(f"[update_environment][Entry] Updating environment {id}")
    
    env = env.copy(update={"url": _normalize_superset_env_url(env.url)})

    # If password is masked, we need the real one for validation
    env_to_validate = env.copy(deep=True)
    if env_to_validate.password == "********":
        old_env = next((e for e in config_manager.get_environments() if e.id == id), None)
        if old_env:
            env_to_validate.password = old_env.password

    # Validate connection before updating (fast path)
    try:
        _validate_superset_connection_fast(env_to_validate)
    except Exception as e:
        logger.error(f"[update_environment][Coherence:Failed] Connection validation failed: {e}")
        raise HTTPException(status_code=400, detail=f"Connection validation failed: {e}")

    if config_manager.update_environment(id, env):
        return env
    raise HTTPException(status_code=404, detail=f"Environment {id} not found")
# [/DEF:update_environment:Function]

# [DEF:delete_environment:Function]
# @PURPOSE: Deletes a Superset environment.
# @PRE:     ID is provided.
# @POST:    Environment is removed from config.
# @PARAM: id (str) - The ID of the environment to delete.
@router.delete("/environments/{id}")
async def delete_environment(
    id: str,
    config_manager: ConfigManager = Depends(get_config_manager)
):
    with belief_scope("delete_environment"):
        logger.info(f"[delete_environment][Entry] Deleting environment {id}")
    config_manager.delete_environment(id)
    return {"message": f"Environment {id} deleted"}
# [/DEF:delete_environment:Function]

# [DEF:test_environment_connection:Function]
# @PURPOSE: Tests the connection to a Superset environment.
# @PRE:     ID is provided.
# @POST:    Returns success or error status.
# @PARAM: id (str) - The ID of the environment to test.
# @RETURN: dict - Success message or error.
@router.post("/environments/{id}/test")
async def test_environment_connection(
    id: str,
    config_manager: ConfigManager = Depends(get_config_manager)
):
    with belief_scope("test_environment_connection"):
        logger.info(f"[test_environment_connection][Entry] Testing environment {id}")
    
    # Find environment
    env = next((e for e in config_manager.get_environments() if e.id == id), None)
    if not env:
        raise HTTPException(status_code=404, detail=f"Environment {id} not found")
    
    try:
        _validate_superset_connection_fast(env)
        
        logger.info(f"[test_environment_connection][Coherence:OK] Connection successful for {id}")
        return {"status": "success", "message": "Connection successful"}
    except Exception as e:
        logger.error(f"[test_environment_connection][Coherence:Failed] Connection failed for {id}: {e}")
        return {"status": "error", "message": str(e)}
# [/DEF:test_environment_connection:Function]

# [DEF:get_logging_config:Function]
# @PURPOSE: Retrieves current logging configuration.
# @PRE:     Config manager is available.
# @POST:    Returns logging configuration.
# @RETURN: LoggingConfigResponse - The current logging config.
@router.get("/logging", response_model=LoggingConfigResponse)
async def get_logging_config(
    config_manager: ConfigManager = Depends(get_config_manager),
    _ = Depends(has_permission("admin:settings", "READ"))
):
    with belief_scope("get_logging_config"):
        logging_config = config_manager.get_config().settings.logging
        return LoggingConfigResponse(
            level=logging_config.level,
            task_log_level=logging_config.task_log_level,
            enable_belief_state=logging_config.enable_belief_state
        )
# [/DEF:get_logging_config:Function]

# [DEF:update_logging_config:Function]
# @PURPOSE: Updates logging configuration.
# @PRE:     New logging config is provided.
# @POST:    Logging configuration is updated and saved.
# @PARAM: config (LoggingConfig) - The new logging configuration.
# @RETURN: LoggingConfigResponse - The updated logging config.
@router.patch("/logging", response_model=LoggingConfigResponse)
async def update_logging_config(
    config: LoggingConfig,
    config_manager: ConfigManager = Depends(get_config_manager),
    _ = Depends(has_permission("admin:settings", "WRITE"))
):
    with belief_scope("update_logging_config"):
        logger.info(f"[update_logging_config][Entry] Updating logging config: level={config.level}, task_log_level={config.task_log_level}")
        
        # Get current settings and update logging config
        settings = config_manager.get_config().settings
        settings.logging = config
        config_manager.update_global_settings(settings)
        
        return LoggingConfigResponse(
            level=config.level,
            task_log_level=config.task_log_level,
            enable_belief_state=config.enable_belief_state
        )
# [/DEF:update_logging_config:Function]

# [DEF:ConsolidatedSettingsResponse:Class]
class ConsolidatedSettingsResponse(BaseModel):
    environments: List[dict]
    connections: List[dict]
    llm: dict
    llm_providers: List[dict]
    logging: dict
    storage: dict
    notifications: dict = {}
# [/DEF:ConsolidatedSettingsResponse:Class]

# [DEF:get_consolidated_settings:Function]
# @PURPOSE: Retrieves all settings categories in a single call
# @PRE:     Config manager is available.
# @POST:    Returns all consolidated settings.
# @RETURN: ConsolidatedSettingsResponse - All settings categories.
@router.get("/consolidated", response_model=ConsolidatedSettingsResponse)
async def get_consolidated_settings(
    config_manager: ConfigManager = Depends(get_config_manager),
    _ = Depends(has_permission("admin:settings", "READ"))
):
    with belief_scope("get_consolidated_settings"):
        logger.info("[get_consolidated_settings][Entry] Fetching all consolidated settings")
        
        config = config_manager.get_config()
        
        from ...services.llm_provider import LLMProviderService
        from ...core.database import SessionLocal
        db = SessionLocal()
        notifications_payload = {}
        try:
            llm_service = LLMProviderService(db)
            providers = llm_service.get_all_providers()
            llm_providers_list = [
                {
                    "id": p.id,
                    "provider_type": p.provider_type,
                    "name": p.name,
                    "base_url": p.base_url,
                    "api_key": "********",
                    "default_model": p.default_model,
                    "is_active": p.is_active
                } for p in providers
            ]

            config_record = db.query(AppConfigRecord).filter(AppConfigRecord.id == "global").first()
            if config_record and isinstance(config_record.payload, dict):
                notifications_payload = config_record.payload.get("notifications", {}) or {}
        finally:
            db.close()

        normalized_llm = normalize_llm_settings(config.settings.llm)

        return ConsolidatedSettingsResponse(
            environments=[env.dict() for env in config.environments],
            connections=config.settings.connections,
            llm=normalized_llm,
            llm_providers=llm_providers_list,
            logging=config.settings.logging.dict(),
            storage=config.settings.storage.dict(),
            notifications=notifications_payload
        )
# [/DEF:get_consolidated_settings:Function]

# [DEF:update_consolidated_settings:Function]
# @PURPOSE: Bulk update application settings from the consolidated view.
# @PRE:     User has admin permissions, config is valid.
# @POST:    Settings are updated and saved via ConfigManager.
@router.patch("/consolidated")
async def update_consolidated_settings(
    settings_patch: dict,
    config_manager: ConfigManager = Depends(get_config_manager),
    _ = Depends(has_permission("admin:settings", "WRITE"))
):
    with belief_scope("update_consolidated_settings"):
        logger.info("[update_consolidated_settings][Entry] Applying consolidated settings patch")
        
        current_config = config_manager.get_config()
        current_settings = current_config.settings
        
        # Update connections if provided
        if "connections" in settings_patch:
            current_settings.connections = settings_patch["connections"]
            
        # Update LLM if provided
        if "llm" in settings_patch:
            current_settings.llm = normalize_llm_settings(settings_patch["llm"])
            
        # Update Logging if provided
        if "logging" in settings_patch:
            current_settings.logging = LoggingConfig(**settings_patch["logging"])
            
        # Update Storage if provided
        if "storage" in settings_patch:
            new_storage = StorageConfig(**settings_patch["storage"])
            is_valid, message = config_manager.validate_path(new_storage.root_path)
            if not is_valid:
                 raise HTTPException(status_code=400, detail=message)
            current_settings.storage = new_storage

        if "notifications" in settings_patch:
            payload = config_manager.get_payload()
            payload["notifications"] = settings_patch["notifications"]
            config_manager.save_config(payload)

        config_manager.update_global_settings(current_settings)
        return {"status": "success", "message": "Settings updated"}
# [/DEF:update_consolidated_settings:Function]

# [DEF:get_validation_policies:Function]
# @PURPOSE: Lists all validation policies.
# @RETURN: List[ValidationPolicyResponse] - List of policies.
@router.get("/automation/policies", response_model=List[ValidationPolicyResponse])
async def get_validation_policies(
    db: Session = Depends(get_db),
    _ = Depends(has_permission("admin:settings", "READ"))
):
    with belief_scope("get_validation_policies"):
        return db.query(ValidationPolicy).all()
# [/DEF:get_validation_policies:Function]

# [DEF:create_validation_policy:Function]
# @PURPOSE: Creates a new validation policy.
# @PARAM: policy (ValidationPolicyCreate) - The policy data.
# @RETURN: ValidationPolicyResponse - The created policy.
@router.post("/automation/policies", response_model=ValidationPolicyResponse)
async def create_validation_policy(
    policy: ValidationPolicyCreate,
    db: Session = Depends(get_db),
    _ = Depends(has_permission("admin:settings", "WRITE"))
):
    with belief_scope("create_validation_policy"):
        db_policy = ValidationPolicy(**policy.dict())
        db.add(db_policy)
        db.commit()
        db.refresh(db_policy)
        return db_policy
# [/DEF:create_validation_policy:Function]

# [DEF:update_validation_policy:Function]
# @PURPOSE: Updates an existing validation policy.
# @PARAM: id (str) - The ID of the policy to update.
# @PARAM: policy (ValidationPolicyUpdate) - The updated policy data.
# @RETURN: ValidationPolicyResponse - The updated policy.
@router.patch("/automation/policies/{id}", response_model=ValidationPolicyResponse)
async def update_validation_policy(
    id: str,
    policy: ValidationPolicyUpdate,
    db: Session = Depends(get_db),
    _ = Depends(has_permission("admin:settings", "WRITE"))
):
    with belief_scope("update_validation_policy"):
        db_policy = db.query(ValidationPolicy).filter(ValidationPolicy.id == id).first()
        if not db_policy:
            raise HTTPException(status_code=404, detail="Policy not found")
        
        update_data = policy.dict(exclude_unset=True)
        for key, value in update_data.items():
            setattr(db_policy, key, value)
        
        db.commit()
        db.refresh(db_policy)
        return db_policy
# [/DEF:update_validation_policy:Function]

# [DEF:delete_validation_policy:Function]
# @PURPOSE: Deletes a validation policy.
# @PARAM: id (str) - The ID of the policy to delete.
@router.delete("/automation/policies/{id}")
async def delete_validation_policy(
    id: str,
    db: Session = Depends(get_db),
    _ = Depends(has_permission("admin:settings", "WRITE"))
):
    with belief_scope("delete_validation_policy"):
        db_policy = db.query(ValidationPolicy).filter(ValidationPolicy.id == id).first()
        if not db_policy:
            raise HTTPException(status_code=404, detail="Policy not found")
        
        db.delete(db_policy)
        db.commit()
        return {"message": "Policy deleted"}
# [/DEF:delete_validation_policy:Function]

# [/DEF:SettingsRouter:Module]
