from decimal import ROUND_DOWN, Decimal, InvalidOperation

from autocomplete.core import register
from autocomplete.shortcuts import ModelAutocomplete
from autocomplete.widgets import AutocompleteWidget
from django import forms

from .models import Park
from .models.location import ParkLocation


@register
class ParkAutocomplete(ModelAutocomplete):
    """Autocomplete for searching parks.

    Features:
    - Name-based search with partial matching
    - Prefetches related owner data
    - Applies standard park queryset filtering
    - Includes park status and location in results
    """

    model = Park
    search_attrs = ["name"]  # We'll match on park names


class ParkSearchForm(forms.Form):
    """Form for searching parks with autocomplete."""

    park = forms.ModelChoiceField(
        queryset=Park.objects.all(),
        required=False,
        widget=AutocompleteWidget(
            ac_class=ParkAutocomplete,
            attrs={
                "class": (
                    "w-full border-gray-300 rounded-lg form-input "
                    "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                ),
                "placeholder": "Search parks...",
            },
        ),
    )


class ParkForm(forms.ModelForm):
    """Form for creating and updating Park objects with location support"""

    # Location fields
    latitude = forms.DecimalField(
        max_digits=9,
        decimal_places=6,
        required=False,
        widget=forms.HiddenInput(),
    )
    longitude = forms.DecimalField(
        max_digits=10,
        decimal_places=6,
        required=False,
        widget=forms.HiddenInput(),
    )
    street_address = forms.CharField(
        max_length=255,
        required=False,
        widget=forms.TextInput(
            attrs={
                "class": (
                    "w-full border-gray-300 rounded-lg form-input "
                    "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                )
            }
        ),
    )
    city = forms.CharField(
        max_length=255,
        required=False,
        widget=forms.TextInput(
            attrs={
                "class": (
                    "w-full border-gray-300 rounded-lg form-input "
                    "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                )
            }
        ),
    )
    state = forms.CharField(
        max_length=255,
        required=False,
        widget=forms.TextInput(
            attrs={
                "class": (
                    "w-full border-gray-300 rounded-lg form-input "
                    "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                )
            }
        ),
    )
    country = forms.CharField(
        max_length=255,
        required=False,
        widget=forms.TextInput(
            attrs={
                "class": (
                    "w-full border-gray-300 rounded-lg form-input "
                    "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                )
            }
        ),
    )
    postal_code = forms.CharField(
        max_length=20,
        required=False,
        widget=forms.TextInput(
            attrs={
                "class": (
                    "w-full border-gray-300 rounded-lg form-input "
                    "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                )
            }
        ),
    )

    class Meta:
        model = Park
        fields = [
            "name",
            "description",
            "operator",
            "property_owner",
            "status",
            "opening_date",
            "closing_date",
            "operating_season",
            "size_acres",
            "website",
            # Location fields handled separately
            "latitude",
            "longitude",
            "street_address",
            "city",
            "state",
            "country",
            "postal_code",
        ]
        widgets = {
            "name": forms.TextInput(
                attrs={
                    "class": (
                        "w-full border-gray-300 rounded-lg form-input "
                        "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                    )
                }
            ),
            "description": forms.Textarea(
                attrs={
                    "class": (
                        "w-full border-gray-300 rounded-lg form-textarea "
                        "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                    ),
                    "rows": 2,
                }
            ),
            "operator": forms.Select(
                attrs={
                    "class": (
                        "w-full border-gray-300 rounded-lg form-select "
                        "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                    )
                }
            ),
            "property_owner": forms.Select(
                attrs={
                    "class": (
                        "w-full border-gray-300 rounded-lg form-select "
                        "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                    )
                }
            ),
            "status": forms.Select(
                attrs={
                    "class": (
                        "w-full border-gray-300 rounded-lg form-select "
                        "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                    )
                }
            ),
            "opening_date": forms.DateInput(
                attrs={
                    "type": "date",
                    "class": (
                        "w-full border-gray-300 rounded-lg form-input "
                        "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                    ),
                }
            ),
            "closing_date": forms.DateInput(
                attrs={
                    "type": "date",
                    "class": (
                        "w-full border-gray-300 rounded-lg form-input "
                        "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                    ),
                }
            ),
            "operating_season": forms.TextInput(
                attrs={
                    "class": (
                        "w-full border-gray-300 rounded-lg form-input "
                        "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                    ),
                    "placeholder": "e.g., Year-round, Summer only, etc.",
                }
            ),
            "size_acres": forms.NumberInput(
                attrs={
                    "class": (
                        "w-full border-gray-300 rounded-lg form-input "
                        "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                    ),
                    "step": "0.01",
                    "min": "0",
                }
            ),
            "website": forms.URLInput(
                attrs={
                    "class": (
                        "w-full border-gray-300 rounded-lg form-input "
                        "dark:border-gray-600 dark:bg-gray-700 dark:text-white"
                    ),
                    "placeholder": "https://example.com",
                }
            ),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Pre-fill location fields if editing existing park
        if self.instance and self.instance.pk and self.instance.location.exists():
            location = self.instance.location.first()
            self.fields["latitude"].initial = location.latitude
            self.fields["longitude"].initial = location.longitude
            self.fields["street_address"].initial = location.street_address
            self.fields["city"].initial = location.city
            self.fields["state"].initial = location.state
            self.fields["country"].initial = location.country
            self.fields["postal_code"].initial = location.postal_code

    def clean_latitude(self):
        latitude = self.cleaned_data.get("latitude")
        if latitude is not None:
            try:
                # Convert to Decimal for precise handling
                latitude = Decimal(str(latitude))
                # Round to exactly 6 decimal places
                latitude = latitude.quantize(Decimal("0.000001"), rounding=ROUND_DOWN)

                # Validate range
                if latitude < -90 or latitude > 90:
                    raise forms.ValidationError("Latitude must be between -90 and 90 degrees.")

                # Convert to string to preserve exact decimal places
                return str(latitude)
            except (InvalidOperation, TypeError) as e:
                raise forms.ValidationError("Invalid latitude value.") from e
        return latitude

    def clean_longitude(self):
        longitude = self.cleaned_data.get("longitude")
        if longitude is not None:
            try:
                # Convert to Decimal for precise handling
                longitude = Decimal(str(longitude))
                # Round to exactly 6 decimal places
                longitude = longitude.quantize(Decimal("0.000001"), rounding=ROUND_DOWN)

                # Validate range
                if longitude < -180 or longitude > 180:
                    raise forms.ValidationError("Longitude must be between -180 and 180 degrees.")

                # Convert to string to preserve exact decimal places
                return str(longitude)
            except (InvalidOperation, TypeError) as e:
                raise forms.ValidationError("Invalid longitude value.") from e
        return longitude

    def save(self, commit=True):
        park = super().save(commit=False)

        # Prepare location data
        location_data = {
            "name": park.name,
            "location_type": "park",
            "latitude": self.cleaned_data.get("latitude"),
            "longitude": self.cleaned_data.get("longitude"),
            "street_address": self.cleaned_data.get("street_address"),
            "city": self.cleaned_data.get("city"),
            "state": self.cleaned_data.get("state"),
            "country": self.cleaned_data.get("country"),
            "postal_code": self.cleaned_data.get("postal_code"),
        }

        # Handle location: update if exists, create if not
        try:
            park_location = park.location
            # Update existing location
            for key, value in location_data.items():
                if key in ["latitude", "longitude"] and value:
                    continue  # Handle coordinates separately
                if hasattr(park_location, key):
                    setattr(park_location, key, value)

            # Handle coordinates if provided
            if "latitude" in location_data and "longitude" in location_data:  # noqa: SIM102
                if location_data["latitude"] and location_data["longitude"]:
                    park_location.set_coordinates(
                        float(location_data["latitude"]),
                        float(location_data["longitude"]),
                    )
            park_location.save()
        except ParkLocation.DoesNotExist:
            # Create new ParkLocation
            coordinates_data = {}
            if "latitude" in location_data and "longitude" in location_data:  # noqa: SIM102
                if location_data["latitude"] and location_data["longitude"]:
                    coordinates_data = {
                        "latitude": float(location_data["latitude"]),
                        "longitude": float(location_data["longitude"]),
                    }

            # Remove coordinate fields from location_data for creation
            creation_data = {k: v for k, v in location_data.items() if k not in ["latitude", "longitude"]}
            creation_data.setdefault("country", "USA")

            park_location = ParkLocation.objects.create(park=park, **creation_data)

            if coordinates_data:
                park_location.set_coordinates(coordinates_data["latitude"], coordinates_data["longitude"])
                park_location.save()

        if commit:
            park.save()

        return park
