from odoo import models, fields, api, _
from math import radians, sin, cos, sqrt, atan2


CATEGORY_MAP = {
    'IN_PASSENGER_VEHICLE': 'In Vehicle',
    'IN_VEHICLE': 'In Vehicle',
    'IN_ROAD_VEHICLE': 'In Vehicle',
    'IN_RAIL_VEHICLE': 'Rail / Transit',
    'IN_TWO_WHEELER_VEHICLE': 'Motorcycle / Scooter',
    'WALKING': 'Walking',
    'ON_FOOT': 'Walking',
    'RUNNING': 'Running',
    'ON_BICYCLE': 'Cycling',
    'STILL': 'Stationary',
    'UNKNOWN': 'Unknown',
}

GEOCODE_BATCH_SIZE = 20


class WtLocationLog(models.Model):
    _name = 'wt.location.log'
    _description = 'WorkTrace Location Log'
    _order = 'arrived_at asc'

    name = fields.Char(string='Name', compute='_compute_name', store=True)
    date = fields.Date(string='Date', required=True, index=True)
    arrived_at = fields.Datetime(string='Begin Time', required=True)
    departed_at = fields.Datetime(string='End Time')

    latitude = fields.Float(string='Latitude', digits=(10, 7))
    longitude = fields.Float(string='Longitude', digits=(10, 7))
    address = fields.Char(string='Address')
    place_name = fields.Char(string='Place / Business Name')
    category = fields.Char(string='Category')

    time_at_location = fields.Float(
        string='Duration (hrs)',
        compute='_compute_time_at_location',
        store=True
    )
    distance_from_previous = fields.Float(string='Distance (mi)', digits=(10, 2))
    travel_time_from_previous = fields.Float(string='Travel Time (hrs)', digits=(10, 2))
    travel_mode = fields.Selection([
        ('driving', 'Driving'),
        ('walking', 'Walking'),
        ('cycling', 'Cycling'),
        ('transit', 'Transit'),
        ('flying', 'Flying'),
        ('unknown', 'Unknown'),
    ], string='Travel Mode', default='unknown')

    source = fields.Selection([
        ('google_timeline', 'Google Timeline'),
        ('manual', 'Manual'),
    ], string='Source', default='google_timeline')

    calendar_event_id = fields.Many2one('calendar.event', string='Calendar Event', ondelete='set null')
    add_to_calendar = fields.Boolean(string='Add to Calendar', default=True)
    notes = fields.Text(string='Notes')

    @api.depends('place_name', 'address', 'latitude', 'longitude')
    def _compute_name(self):
        for rec in self:
            rec.name = (
                rec.place_name
                or rec.address
                or (f'{rec.latitude:.5f}, {rec.longitude:.5f}' if rec.latitude else 'Unknown Location')
            )

    @api.depends('arrived_at', 'departed_at')
    def _compute_time_at_location(self):
        for rec in self:
            if rec.arrived_at and rec.departed_at:
                rec.time_at_location = (rec.departed_at - rec.arrived_at).total_seconds() / 3600
            else:
                rec.time_at_location = 0.0

    @staticmethod
    def _haversine_distance(lat1, lon1, lat2, lon2):
        R = 3958.8
        lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
        dlat, dlon = lat2 - lat1, lon2 - lon1
        a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
        return R * 2 * atan2(sqrt(a), sqrt(1 - a))

    def action_geocode(self):
        """Resolve coordinates to address using OpenStreetMap Nominatim.
        Processes up to GEOCODE_BATCH_SIZE records per call to avoid HTTP worker timeouts.
        """
        import requests
        import time

        to_geocode = self.filtered(lambda r: r.latitude and r.longitude and not r.address)
        if not to_geocode:
            to_geocode = self.filtered(lambda r: r.latitude and r.longitude)

        batch = to_geocode[:GEOCODE_BATCH_SIZE]
        processed = 0

        for rec in batch:
            url = 'https://nominatim.openstreetmap.org/reverse'
            params = {'lat': rec.latitude, 'lon': rec.longitude, 'format': 'json', 'addressdetails': 1}
            headers = {'User-Agent': 'WorkTrace/1.0 (Odoo Module)'}
            try:
                resp = requests.get(url, params=params, headers=headers, timeout=10)
                resp.raise_for_status()
                result = resp.json()
                addr = result.get('address', {})

                rec.address = result.get('display_name', '')
                rec.place_name = rec.place_name or (
                    result.get('name')
                    or addr.get('amenity')
                    or addr.get('shop')
                    or addr.get('building')
                    or addr.get('office')
                    or addr.get('tourism')
                    or ''
                )
                osm_type = (
                    addr.get('amenity') or addr.get('shop') or addr.get('tourism')
                    or addr.get('leisure') or addr.get('office') or addr.get('building')
                    or result.get('type', '') or result.get('class', '')
                )
                if osm_type and not rec.category:
                    rec.category = osm_type.replace('_', ' ').title()

                processed += 1
                time.sleep(1)
            except Exception:
                pass

        remaining = self.search_count([('latitude', '!=', 0), ('address', '=', False)])

        if remaining:
            msg = _('%d address(es) resolved. %d still need geocoding â€” click "Geocode Next %d" again to continue.') % (
                processed, remaining, GEOCODE_BATCH_SIZE)
            notif_type = 'warning'
        else:
            msg = _('All done! %d address(es) resolved.') % processed
            notif_type = 'success'

        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': _('Geocoding Batch Complete'),
                'message': msg,
                'type': notif_type,
                'sticky': True,
            },
        }

    def action_geocode_all_pending(self):
        """Geocode the next batch of ALL unresolved records (ignores selection)."""
        pending = self.search([('latitude', '!=', 0), ('address', '=', False)], limit=GEOCODE_BATCH_SIZE)
        if not pending:
            return {
                'type': 'ir.actions.client',
                'tag': 'display_notification',
                'params': {
                    'title': _('Nothing to Geocode'),
                    'message': _('All records with coordinates already have addresses.'),
                    'type': 'info',
                    'sticky': False,
                },
            }
        return pending.action_geocode()

    def action_compute_distances(self):
        """Recompute distances and travel times for all logs grouped by date."""
        dates = self.mapped('date')
        for date in set(dates):
            logs = self.search([('date', '=', date)], order='arrived_at asc')
            prev = None
            for log in logs:
                if prev and prev.latitude and log.latitude:
                    log.distance_from_previous = self._haversine_distance(
                        prev.latitude, prev.longitude, log.latitude, log.longitude
                    )
                    if prev.departed_at and log.arrived_at:
                        delta = log.arrived_at - prev.departed_at
                        log.travel_time_from_previous = max(delta.total_seconds() / 3600, 0)
                prev = log