#!/usr/bin/env perl

# Print the historic weather for a place, e.g. "weather 2022-06-03 Buxton, NC, USA"

use strict;
use warnings;
use Cwd qw(realpath);
use File::Basename;
use File::HomeDir;
use File::Spec;
use Geo::Coder::Free;
use Geo::Coder::Free::Local;
use Geo::Coder::Free::OpenAddresses;
use LWP::UserAgent::Cached;
use Weather::Meteo;

binmode(STDOUT, ':encoding(UTF-8)');

if(scalar(@ARGV) <= 1) {
	die "Usage: $0 YYYY-MM-DD location\n";
}

my $date     = shift @ARGV;
my $location = join(' ', @ARGV);

my $coordinates = Geo::Coder::Free::Local->new()->geocode($location) //
	Geo::Coder::Free->new()->geocode($location);

if(!defined($coordinates) && $ENV{'OPENADDR_HOME'}) {
	# Untaint and canonicalise the path before handing it to the geocoder
	my ($safe_oa) = ($ENV{'OPENADDR_HOME'} =~ m{\A([\w/.\-]+)\z})
		or die "OPENADDR_HOME contains invalid characters\n";
	my $real_oa = realpath($safe_oa)
		or die "OPENADDR_HOME '$safe_oa' cannot be resolved: $!\n";
	$coordinates = Geo::Coder::Free::OpenAddresses->new(
		openaddr => $real_oa,
	)->geocode(location => $location);
}

if(!defined($coordinates)) {
	die "$0: unknown location '$location'\n";
}

# Determine cache directory: $CACHE_DIR env var or ~/.cache/lwp-cache
my $cache_dir;
if($ENV{'CACHE_DIR'}) {
	# Untaint: accept only printable path characters; reject shell metacharacters
	my ($safe_dir) = ($ENV{'CACHE_DIR'} =~ m{\A([\w/.\-]+)\z})
		or die "CACHE_DIR contains invalid characters\n";
	# Canonicalise via realpath to prevent ../.. traversal attacks
	my $real_dir = realpath($safe_dir)
		or die "CACHE_DIR '$safe_dir' cannot be resolved: $!\n";
	$cache_dir = $real_dir;
	# Atomic mkdir: ignore EEXIST from concurrent creation (avoids TOCTOU race)
	unless(mkdir($cache_dir, 0700) || $!{EEXIST}) {
		die "$cache_dir: $!\n";
	}
	$cache_dir = File::Spec->catfile($cache_dir, 'lwp-cache');
} else {
	$cache_dir = File::Spec->catfile(File::HomeDir->my_home(), '.cache', 'lwp-cache');
}

# Atomic mkdir for the final cache leaf directory
unless(mkdir($cache_dir, 0700) || $!{EEXIST}) {
	die "$cache_dir: $!\n";
}

my $cached_browser = LWP::UserAgent::Cached->new(
	cache_dir  => $cache_dir,
	agent      => basename($0),
	keep_alive => 1,
	timeout    => 30,
);
$cached_browser->env_proxy(1);
$cached_browser->conn_cache->total_capacity(undef);

my $weather = Weather::Meteo->new(ua => $cached_browser)->weather($coordinates, $date);

if(!defined($weather)) {
	die "$0: no weather data available for '$location' on $date\n";
}

print 'Maximum temperature: ',
	$weather->{'daily'}->{'temperature_2m_max'}[0],
	($weather->{'daily_units'}->{'temperature_2m_max'} // ''), "\n";
print 'Maximum wind speed ',
	$weather->{'daily'}->{'windspeed_10m_max'}[0],
	($weather->{'daily_units'}->{'windspeed_10m_max'} // ''), "\n";
print 'Maximum wind gust speed ',
	$weather->{'daily'}->{'windgusts_10m_max'}[0],
	($weather->{'daily_units'}->{'windgusts_10m_max'} // ''), "\n";
