"""
Service for interacting with NBN Atlas API to fetch occurrence records.
"""
import logging
import requests
from typing import List, Dict, Any, Optional, TypedDict
from urllib.parse import urlparse, parse_qs, urlencode
from django.conf import settings
logger = logging.getLogger(__name__)

PAGE_SIZE = settings.RECORD_CLEANER_MAX_RECORDS



# NBN Atlas API endpoints
ATLAS_OCCURRENCE_SEARCH_BASE_URL = 'https://records-ws.nbnatlas.org/occurrences/search'
REGISTRY_DATA_RESOURCES_URL = 'https://registry.nbnatlas.org/ws/dataResource'

# Default fields to fetch from Atlas
DEFAULT_FIELDS = [
    'id',
    'occurrenceID',
    'taxonConceptID',
    'scientificName',
    'eventDate',
    'eventDateEnd',
    'gridReference',
    'decimalLatitude',
    'decimalLongitude',
    'coordinateUncertaintyInMeters',
    'lifeStage'
]


class Occurrence(TypedDict):
    """
    Type definition for a normalized NBN Atlas occurrence record.
    All fields from DEFAULT_FIELDS are always present (may be empty strings).
    """
    id: str  # Mapped from 'uuid' in API response
    occurrenceID: str
    taxonConceptID: str
    scientificName: str
    eventDate: str  # UTC timestamp in milliseconds (may be empty)
    eventDateEnd: str  # UTC timestamp in milliseconds (may be empty)
    gridReference: str
    decimalLatitude: str
    decimalLongitude: str
    coordinateUncertaintyInMeters: str
    lifeStage: str  # Lowercased, converted from list if needed (may be empty)


def fetch_data_resources() -> List[Dict[str, Any]]:
    """
    Fetch all data resources from NBN Atlas Registry and filter for datasets.

    Returns:
        List of dataset objects with uid, name, and other properties
    """
    try:
        response = requests.get(REGISTRY_DATA_RESOURCES_URL, timeout=30)
        response.raise_for_status()
        data = response.json()

        # Filter for resources that are datasets (resourceType: "records")
        datasets = [
            resource for resource in data
            if resource.get('resourceType') == 'records'
        ]

        return datasets
    except requests.exceptions.RequestException as e:
        logger.error(f"Failed to fetch data resources: {str(e)}", exc_info=True)
        raise Exception(f"NBN Atlas Registry error: Failed to fetch data resources. {str(e)}")


def parse_search_url(search_url: str) -> Dict[str, str]:
    """
    Parse an NBN Atlas search URL and extract q and fq parameters.

    Args:
        search_url: Full URL from records.nbnatlas.org

    Returns:
        Dictionary containing 'q' and 'fq' parameters
    """
    parsed = urlparse(search_url)
    query_params = parse_qs(parsed.query)

    # Extract q parameter (single value)
    q = query_params.get('q', ['*:*'])[0]

    # Extract all fq parameters (can be multiple)
    fq = query_params.get('fq', [])

    return {
        'q': q,
        'fq': fq
    }


def build_atlas_search_url(
    data_resource_uid: Optional[str] = None,
    search_url: Optional[str] = None
) -> str:
    """
    Build NBN Atlas occurrence search URL with appropriate filters.

    Args:
        data_resource_uid: Dataset UID to filter by (mutually exclusive with search_url)
        search_url: User-provided search URL (mutually exclusive with data_resource_uid)
        start: Starting offset for pagination

    Returns:
        Complete search URL
    """
    params = {
        'pageSize': PAGE_SIZE,
        'fl': ','.join(DEFAULT_FIELDS),
        # 'sort': 'occurrence_id'
    }

    if data_resource_uid:
        # Dataset was selected
        params['q'] = '*:*'
        params['fq'] = f'data_resource_uid:{data_resource_uid}'
    elif search_url:
        # Parse user-provided URL
        parsed_params = parse_search_url(search_url)
        params['q'] = parsed_params['q']
        if parsed_params['fq']:
            params['fq'] = parsed_params['fq']
    else:
        # Default: all records
        params['q'] = '*:*'

    return f"{ATLAS_OCCURRENCE_SEARCH_BASE_URL}?{urlencode(params, doseq=True)}"


def normalize_occurrence(occurrence: Dict[str, Any]) -> Occurrence:
    """
    Normalize an occurrence record to ensure all DEFAULT_FIELDS are present.
    Maps 'uuid' from API response to 'id' field.
    
    Args:
        occurrence: Raw occurrence record from Atlas API
        
    Returns:
        Normalized occurrence record with all DEFAULT_FIELDS populated
    """
    normalized = {}
    
    for field in DEFAULT_FIELDS:
        if field == 'id':
            # Map 'uuid' from API response to 'id'
            normalized['id'] = occurrence.get('uuid', '')
        else:
            normalized[field] = occurrence.get(field, '')
    
    # Handle lifeStage normalization (convert list to string, lowercase)
    if 'lifeStage' in occurrence:
        if isinstance(occurrence['lifeStage'], list):
            normalized['lifeStage'] = occurrence['lifeStage'][0].lower() if occurrence['lifeStage'] else ''
        elif isinstance(occurrence['lifeStage'], str):
            normalized['lifeStage'] = occurrence['lifeStage'].lower()
    
    return normalized


def fetch_atlas_occurrences(
    data_resource_uid: Optional[str] = None,
    search_url: Optional[str] = None
) -> List[Occurrence]:
    """
    Fetch occurrence records from NBN Atlas.

    Args:
        data_resource_uid: Dataset UID to filter by
        search_url: User-provided search URL

    Returns:
        List of normalized occurrence records with all DEFAULT_FIELDS populated
    """
   
    url = build_atlas_search_url(
        data_resource_uid=data_resource_uid,
        search_url=search_url
    )

   
    try:
        response = requests.get(url, timeout=60)
        response.raise_for_status()
        data = response.json()

        occurrences = data.get('occurrences', [])
        total_records = data.get('totalRecords', 0)

    except requests.exceptions.RequestException as e:
        logger.error(f"Failed to fetch occurrences from Atlas: {str(e)}", exc_info=True)
        raise Exception(f"NBN Atlas API error: Failed to fetch occurrence records. {str(e)}")

    logger.info(f"Fetched {len(occurrences)} occurrences from Atlas")

    # Normalize each occurrence to ensure all DEFAULT_FIELDS are present
    normalized_occurrences: List[Occurrence] = [normalize_occurrence(occurrence) for occurrence in occurrences]
    
    return normalized_occurrences
