"""Configuration loading and validation."""
from pathlib import Path
from typing import Any, Dict, List, Optional
import yaml
import os
import requests
from urllib.parse import urlparse


class Config:
    """Audit configuration."""
    
    def __init__(self, config_path: str):
        """Load configuration from YAML file."""
        self.config_path = Path(config_path)
        if not self.config_path.exists():
            raise FileNotFoundError(f"Config file not found: {config_path}")
        
        with open(self.config_path, 'r') as f:
            self.data = yaml.safe_load(f)
        
        # Get mode (new field, defaults to dataset_model for backward compatibility)
        self.mode = self.data.get('mode', 'dataset_model')
        self.domain_profile = self.data.get('domain_profile', 'general')
        self.jurisdiction = self.data.get('jurisdiction', 'United States')
        
        # Mode-specific field handling
        if self.mode == 'dataset_model' or self.mode == 'hybrid':
            # Handle dataset: can be path, URL, or env var reference
            resolved_dataset = self._resolve_dataset()
            if resolved_dataset is None:
                raise ValueError(
                    f"Dataset is required for {self.mode} mode. "
                    f"Provide one of: 'dataset', 'dataset_url', 'dataset_env', or 'dataset_sample'"
                )
            self.dataset = resolved_dataset
            
            # Resolve model path (required for dataset_model and hybrid)
            model_path = self.data.get('model')
            if not model_path:
                raise ValueError("'model' field is required for dataset_model and hybrid modes")
            
            model_path = Path(model_path)
            if model_path.is_absolute():
                self.model = model_path
            else:
                self.model = Path.cwd() / model_path
            
            self.label = self.data.get('label', 'target')
            self.sensitive = self.data.get('sensitive', [])
            self.thresholds = self.data.get('thresholds', {})
            self.task_type = self.data.get('task_type', None)  # binary_classification, multi_class_classification, regression
            self.positive_class = self.data.get('positive_class', None)  # For binary classification
        else:
            # API model mode - dataset and model are optional
            self.dataset = None
            self.model = None
            self.label = None
            self.sensitive = []
            self.thresholds = {}
            
            # Try to resolve dataset if provided (for evaluation)
            # Only if it's explicitly provided and not "NONE"
            dataset_value = self.data.get('dataset')
            if (self.data.get('dataset_url') or self.data.get('dataset_env') or self.data.get('dataset_sample') or 
                (dataset_value and dataset_value != 'NONE' and dataset_value != '')):
                try:
                    resolved = self._resolve_dataset()
                    if resolved is not None:
                        self.dataset = resolved
                except Exception as e:
                    # Dataset optional for API mode - only log if it was explicitly provided
                    if dataset_value and dataset_value != 'NONE' and dataset_value != '':
                        # User provided a dataset but it failed - this is fine for API mode
                        pass
        
        # API-specific fields (for api_model and hybrid modes)
        if self.mode == 'api_model' or self.mode == 'hybrid':
            self.api_provider = self.data.get('api_provider', 'unknown')
            self.api_model = self.data.get('api_model', 'unknown')
            self.api_key_env = self.data.get('api_key_env', 'API_KEY')
            self.test_suite_source = self.data.get('test_suite_source', 'tests/prompts.jsonl')
            self.protected_demographics = self.data.get('protected_demographics', [])
            self.governance_thresholds = self.data.get('governance_thresholds', {})
        else:
            self.api_provider = None
            self.api_model = None
            self.api_key_env = None
            self.test_suite_source = None
            self.protected_demographics = []
            self.governance_thresholds = {}
        
        # Dataset metadata (required for production audits)
        self.dataset_metadata = self.data.get('dataset_metadata', {})
        self.dataset_description = self.dataset_metadata.get('description', '')
        self.dataset_population = self.dataset_metadata.get('population', '')
        self.dataset_jurisdiction = self.dataset_metadata.get('jurisdiction', '')
        self.dataset_sample_size = self.dataset_metadata.get('sample_size', None)
        
        # Evidence dir is relative to current working directory
        evidence_dir_path = self.data.get('evidence_dir', 'audit_evidence')
        if Path(evidence_dir_path).is_absolute():
            self.evidence_dir = Path(evidence_dir_path)
        else:
            self.evidence_dir = Path.cwd() / evidence_dir_path
        
        # Validate paths (mode-specific)
        if self.mode == 'dataset_model' or self.mode == 'hybrid':
            self._validate_dataset()
            if self.model and not self.model.exists():
                raise FileNotFoundError(f"Model not found: {self.model}")
        elif self.mode == 'api_model':
            # For API mode, validate API configuration
            if not self.api_provider or self.api_provider == 'unknown':
                raise ValueError("'api_provider' field is required for api_model mode")
            if not self.api_key_env:
                raise ValueError("'api_key_env' field is required for api_model mode")
    
    def _resolve_dataset(self) -> Optional[Path]:
        """Resolve dataset path from config, supporting URLs, env vars, and local paths."""
        # Priority: dataset_url > dataset_env > dataset_sample > dataset
        dataset_url = self.data.get('dataset_url')
        dataset_env = self.data.get('dataset_env')
        dataset_sample = self.data.get('dataset_sample')
        dataset_path = self.data.get('dataset')
        
        # For API models, dataset is optional - if it's "NONE" or empty, return None
        if self.mode == 'api_model':
            if not dataset_url and not dataset_env and not dataset_sample:
                if not dataset_path or dataset_path == 'NONE' or dataset_path == '':
                    return None  # API models don't require dataset
        
        # Option 1: URL-based dataset
        if dataset_url:
            return self._download_dataset_from_url(dataset_url)
        
        # Option 2: Environment variable reference
        if dataset_env:
            env_value = os.getenv(dataset_env)
            if not env_value:
                raise ValueError(
                    f"Environment variable '{dataset_env}' not set. "
                    f"Set it to a dataset path or URL."
                )
            # Check if it's a URL
            if self._is_url(env_value):
                return self._download_dataset_from_url(env_value)
            # Otherwise treat as path
            dataset_path = env_value
        
        # Option 3: Sample dataset (for CI)
        if dataset_sample:
            sample_path = Path(dataset_sample)
            if sample_path.is_absolute():
                return sample_path
            return Path.cwd() / sample_path
        
        # Option 4: Regular path (original behavior)
        # For API models, if dataset is "NONE" or empty, it's optional
        if not dataset_path or (self.mode == 'api_model' and (dataset_path == 'NONE' or dataset_path == '')):
            if self.mode == 'api_model':
                return None  # API models don't require dataset
            raise ValueError("One of 'dataset', 'dataset_url', 'dataset_env', or 'dataset_sample' is required")
        
        dataset_path = Path(dataset_path)
        if dataset_path.is_absolute():
            return dataset_path
        return Path.cwd() / dataset_path
    
    def _is_url(self, value: str) -> bool:
        """Check if a string is a URL."""
        try:
            result = urlparse(value)
            return all([result.scheme, result.netloc])
        except:
            return False
    
    def _download_dataset_from_url(self, url: str) -> Path:
        """Download dataset from URL and return local path."""
        from rich.console import Console
        console = Console()
        
        # Determine local filename from URL or use default
        parsed = urlparse(url)
        filename = os.path.basename(parsed.path) or "dataset.csv"
        if not filename.endswith(('.csv', '.pkl', '.json')):
            filename = "dataset.csv"
        
        local_path = Path.cwd() / ".verity_cache" / filename
        local_path.parent.mkdir(parents=True, exist_ok=True)
        
        # Check if already downloaded (simple cache)
        if local_path.exists():
            console.print(f"[dim]Using cached dataset: {local_path}[/dim]")
            return local_path
        
        console.print(f"[cyan]Downloading dataset from {url}...[/cyan]")
        
        try:
            # Handle different URL schemes
            if url.startswith(('http://', 'https://')):
                response = requests.get(url, timeout=300, stream=True)
                response.raise_for_status()
                with open(local_path, 'wb') as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        f.write(chunk)
            elif url.startswith('s3://'):
                # AWS S3 - requires boto3
                try:
                    import boto3
                    s3 = boto3.client('s3')
                    bucket, key = url[5:].split('/', 1)
                    s3.download_file(bucket, key, str(local_path))
                except ImportError:
                    raise ValueError(
                        "S3 URLs require 'boto3'. Install with: pip install boto3"
                    )
            elif url.startswith('gs://'):
                # Google Cloud Storage - requires google-cloud-storage
                try:
                    from google.cloud import storage
                    client = storage.Client()
                    bucket_name, blob_name = url[5:].split('/', 1)
                    bucket = client.bucket(bucket_name)
                    blob = bucket.blob(blob_name)
                    blob.download_to_filename(str(local_path))
                except ImportError:
                    raise ValueError(
                        "GCS URLs require 'google-cloud-storage'. Install with: pip install google-cloud-storage"
                    )
            elif url.startswith('azure://'):
                # Azure Blob Storage - requires azure-storage-blob
                try:
                    from azure.storage.blob import BlobClient
                    # Format: azure://account/container/blob
                    parts = url[8:].split('/', 2)
                    if len(parts) < 3:
                        raise ValueError("Azure URL format: azure://account/container/blob")
                    account, container, blob = parts
                    # Get connection string from env or use account name
                    conn_str = os.getenv('AZURE_STORAGE_CONNECTION_STRING')
                    blob_client = BlobClient.from_connection_string(conn_str, container, blob)
                    with open(local_path, 'wb') as f:
                        blob_client.download_blob().readinto(f)
                except ImportError:
                    raise ValueError(
                        "Azure URLs require 'azure-storage-blob'. Install with: pip install azure-storage-blob"
                    )
            else:
                raise ValueError(f"Unsupported URL scheme: {url}")
            
            console.print(f"[green]Dataset downloaded to: {local_path}[/green]")
            return local_path
            
        except Exception as e:
            if local_path.exists():
                local_path.unlink()
            raise ValueError(f"Failed to download dataset from {url}: {e}")
    
    def _validate_dataset(self):
        """Validate dataset exists, with helpful error messages."""
        # Skip validation if dataset is None (valid for API models)
        if self.dataset is None:
            return
        if not self.dataset.exists():
            # Check if we're in CI environment
            is_ci = os.getenv('CI') == 'true' or os.getenv('GITHUB_ACTIONS') == 'true'
            
            # Check if file might be gitignored
            gitignore_hint = ""
            if self.config_path.parent.exists():
                gitignore_path = self.config_path.parent / '.gitignore'
                if gitignore_path.exists():
                    with open(gitignore_path, 'r') as f:
                        gitignore_content = f.read()
                        dataset_name = self.dataset.name
                        if dataset_name in gitignore_content or '*.csv' in gitignore_content:
                            gitignore_hint = (
                                f"\n\n💡 This file appears to be in .gitignore. "
                                f"In CI/CD, gitignored files aren't available.\n"
                                f"Solutions:\n"
                                f"  1. Use 'dataset_url' in verity.yml with a secure URL (S3, GCS, etc.)\n"
                                f"  2. Use 'dataset_env' to reference a GitHub Secret containing the URL\n"
                                f"  3. Use 'dataset_sample' for a small sample dataset committed to the repo\n"
                                f"  4. Download the dataset in your workflow before running verity\n"
                            )
            
            error_msg = f"Dataset not found: {self.dataset}"
            if is_ci:
                error_msg += (
                    "\n\n⚠️  Running in CI/CD environment. "
                    "Files in .gitignore are not available in GitHub Actions."
                )
            error_msg += gitignore_hint
            
            error_msg += (
                "\n\nFor more information, see: "
                "https://docs.verity.com/datasets-in-ci"
            )
            
            raise FileNotFoundError(error_msg)
    
    def get_threshold(self, metric_name: str) -> float:
        """Get threshold for a metric, or None if not specified."""
        return self.thresholds.get(metric_name)

