"""
Data mapping utilities for converting between NBN Atlas and Record Cleaner formats.
"""
from typing import Dict, Any, Optional
import math
import re
from components.utils import format_atlas_event_date

IRISH_GRID_RE = re.compile(
    r"^[A-HJ-Z]\s*(?:\d{2}|\d{4}|\d{6}|\d{8})$",
    re.IGNORECASE
)

def map_date(occurrence: Dict[str, Any]) -> str:
    """
    Convert occurrence eventDate (UTC timestamp in milliseconds) to a date string.
    
    If both eventDate and eventDateEnd are present, returns a date range in format
    'DD/MM/YYYY - DD/MM/YYYY'. Otherwise returns a single date in format 'DD/MM/YYYY'.

    Args:
        occurrence: NBN Atlas occurrence record

    Returns:
        Date string in format 'DD/MM/YYYY' or 'DD/MM/YYYY - DD/MM/YYYY' for date ranges
    """
    event_date = occurrence.get('eventDate')
    event_date_end = occurrence.get('eventDateEnd')
    
    start_date = format_atlas_event_date(event_date)
    
    if event_date_end:
        end_date = format_atlas_event_date(event_date_end)
        if start_date and end_date:
            return f"{start_date} - {end_date}"
    
    return start_date


def map_coordinate_uncertainty(uncertainty_in_meters: Optional[float]) -> int:
    """
    Round up coordinate uncertainty to nearest 1, 10, 100, 1000, etc.

    Args:
        uncertainty_in_meters: Coordinate uncertainty from NBN Atlas

    Returns:
        Rounded uncertainty value (1, 10, 100, 1000, etc.)
    """
    if uncertainty_in_meters is None or uncertainty_in_meters <= 0:
        return 1

    try:
        uncertainty = float(uncertainty_in_meters)
        # Calculate the order of magnitude
        magnitude = 10 ** math.floor(math.log10(uncertainty))
        # Round up to the next order of magnitude if needed
        if uncertainty <= magnitude:
            return int(magnitude)
        else:
            return int(magnitude * 10)
    except (ValueError, TypeError):
        return 1


def _is_irish_grid_ref(grid_ref: str) -> bool:
    """    
    Args:
        grid_ref: Grid reference string to check

    Returns:
        True if the grid reference matches Irish Grid pattern, False otherwise
    """
    if not grid_ref:
        return False
    
    return bool(IRISH_GRID_RE.match(grid_ref.strip()))


def _build_spatial_reference(occurrence: Dict[str, Any]) -> Dict[str, Any]:
    """
    Build the spatial reference payload for Record Cleaner.

    Uses SRID 29903 for Irish Grid references, SRID 27700 for British National Grid
    references, and falls back to WGS84 lat/lon if no grid reference is provided.
    """
    grid_ref = occurrence.get('gridReference')
    if grid_ref:
        if _is_irish_grid_ref(grid_ref):
            srid = 29903  # Irish Grid
        else:
            srid = 27700  # British National Grid
        return {
            'srid': srid,
            'gridref': grid_ref
        }

    # Fallback to WGS84 lat/lon if no grid reference provided
    return {
        'srid': 4326,
        'latitude': occurrence.get('decimalLatitude'),
        'longitude': occurrence.get('decimalLongitude'),
        'accuracy': map_coordinate_uncertainty(
            occurrence.get('coordinateUncertaintyInMeters')
        )
    }


def map_occurrence_to_record_cleaner(occurrence: Dict[str, Any], record_id: int) -> Dict[str, Any]:
    """
    Map NBN Atlas occurrence record to Record Cleaner format.

    Field mappings:
    - id: Generated integer ID
    - tvk: taxonConceptID from occurrence
    - name: scientificName or vernacularName when available
    - date: Mapped from eventDate (and eventDateEnd if present for date ranges)
    - sref uses SRID 29903 for Irish Grid references, SRID 27700 for British National Grid
      references when gridReference is supplied
    - sref falls back to latitude/longitude (SRID 4326) if grid references are absent
    - stage: lifeStage (optional)
    
    Args:
        occurrence: NBN Atlas occurrence record
        record_id: Unique integer ID for this record

    Returns:
        Record Cleaner formatted record
    """
    record = {
        'id': record_id,
        'date': map_date(occurrence),
        'sref': _build_spatial_reference(occurrence)
    }

    # Add TVK if available
    tvk = occurrence.get('taxonConceptID')
    if tvk:
        record['tvk'] = tvk

    # Add scientific name as fallback if TVK not available
    if not tvk:
        name = occurrence.get('scientificName')
        if name:
            record['name'] = name
        else:
            common_name = occurrence.get('vernacularName')
            if common_name:
                record['name'] = common_name

    stage = occurrence.get('lifeStage')
    if stage:
        record['stage'] = stage
  

    return record


def merge_validation_and_verification_results(
    occurrence: Dict[str, Any],
    validated: Optional[Dict[str, Any]],
    verified: Optional[Dict[str, Any]]
) -> Dict[str, Any]:
    """
    Merge original occurrence with validation and verification results.

    Args:
        occurrence: Original NBN Atlas occurrence
        validated: Validation result (if any)
        verified: Verification result (if any)

    Returns:
        Merged record with all data
    """
    merged = {
        # Original fields
        'id': occurrence.get('id', ''),
        'occurrenceID': occurrence.get('occurrenceID', ''),
        'taxonConceptID': occurrence.get('taxonConceptID', ''),
        'scientificName': occurrence.get('scientificName', ''),
        'eventDate': occurrence.get('eventDate', ''),
        'eventDateEnd': occurrence.get('eventDateEnd', ''),
        'gridReference': occurrence.get('gridReference', ''),
        'decimalLatitude': occurrence.get('decimalLatitude', ''),
        'decimalLongitude': occurrence.get('decimalLongitude', ''),
        'coordinateUncertaintyInMeters': occurrence.get('coordinateUncertaintyInMeters', ''),
        'lifeStage': occurrence.get('lifeStage', ''),
        'dataResourceID': occurrence.get('dataResourceUid', ''),
    }

    # Add validation results if available
    if validated:
        merged.update({
            'validation_result': validated.get('result', ''),
            'validation_messages': validated.get('messages', []),
            'preferred_tvk': validated.get('preferred_tvk', ''),
        })
    else:
        merged.update({
            'validation_result': 'not_validated',
            'validation_messages': [],
            'preferred_tvk': '',
        })

    # Add verification results if available
    if verified:
        merged.update({
            'verification_result': verified.get('result', ''),
            'verification_messages': verified.get('messages', []),
            # 'organism_key': verified.get('organism_key', ''),
            'id_difficulty': verified.get('id_difficulty', ''),
        })
    else:
        merged.update({
            'verification_result': 'not_verified',
            'verification_messages': [],
            # 'organism_key': '',
            'id_difficulty': '',
        })

    return merged
