<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Repositories\HouseholdRepository;
use App\Repositories\HouseholderNameRepository;
use App\Repositories\TerritoryRepository;
use Core\Controller;
use Core\Pagination;
use Core\Request;
use Core\Response;
use Core\Validator;

class HouseholdController extends Controller
{
    public function index(): Response
    {
        if ($redirect = $this->requireAuth()) {
            return $redirect;
        }

        $request     = Request::capture();
        $search      = (string) ($request->input('search') ?? '');
        $territoryId = (string) ($request->input('territory_id') ?? '');
        $doNotCall   = (string) ($request->input('do_not_call') ?? '');
        $page        = max(1, (int) ($request->input('page') ?? 1));
        $perPage     = 20;

        $repo       = new HouseholdRepository(database());
        $total      = $repo->countAll($search, $territoryId, $doNotCall);
        $pagination = new Pagination($total, $page, $perPage);
        $households = $repo->findPaged($page, $perPage, $search, $territoryId, $doNotCall);
        $territories = (new TerritoryRepository(database()))->allOrdered();

        return $this->view('households.index', [
            'pageTitle'   => 'Households',
            'households'  => $households,
            'territories' => $territories,
            'search'      => $search,
            'territoryId' => $territoryId,
            'doNotCall'   => $doNotCall,
            'pagination'  => $pagination,
        ]);
    }

    public function show(int|string $id): Response
    {
        if ($redirect = $this->requireAuth()) {
            return $redirect;
        }

        $household = (new HouseholdRepository(database()))->findWithTerritory((int) $id);

        if (!$household) {
            return Response::notFound('Household not found.');
        }

        $names = (new HouseholderNameRepository(database()))->findAllByHousehold((int) $id);
        $maps  = maps_config();

        return $this->view('households.show', [
            'pageTitle' => e($household['address']),
            'household' => $household,
            'names'     => $names,
            'maps'      => $maps,
        ]);
    }

    public function create(): Response
    {
        if ($redirect = $this->requireAuth()) {
            return $redirect;
        }

        $request     = Request::capture();
        $territories = (new TerritoryRepository(database()))->allOrdered();

        return $this->view('households.create', [
            'pageTitle'          => 'New Household',
            'territories'        => $territories,
            'defaultTerritoryId' => (string) ($request->input('territory_id') ?? ''),
            'errors'             => [],
            'old'                => [],
        ]);
    }

    public function store(): Response
    {
        if ($redirect = $this->requireAuth()) {
            return $redirect;
        }

        $request     = Request::capture();
        $territories = (new TerritoryRepository(database()))->allOrdered();

        if (!verify_csrf_token($request->input('_token'))) {
            return $this->view('households.create', [
                'pageTitle'          => 'New Household',
                'territories'        => $territories,
                'defaultTerritoryId' => '',
                'errors'             => ['_token' => ['Invalid request. Please try again.']],
                'old'                => $request->all(),
            ]);
        }

        $fields = $this->extractFields($request);

        $validator = (new Validator())
            ->required('territory_id', $fields['territory_id'], 'Territory is required.')
            ->required('address', $fields['address'], 'Address is required.')
            ->maxLength('address', $fields['address'], 255)
            ->optionalNumeric('street_number', $fields['street_number'])
            ->optionalNumeric('latitude', $fields['latitude'], 'Latitude must be a number.')
            ->optionalNumeric('longitude', $fields['longitude'], 'Longitude must be a number.');

        if ($validator->fails()) {
            return $this->view('households.create', [
                'pageTitle'          => 'New Household',
                'territories'        => $territories,
                'defaultTerritoryId' => $fields['territory_id'],
                'errors'             => $validator->errors(),
                'old'                => $request->all(),
            ]);
        }

        $now = date('Y-m-d H:i:s');
        database()->execute(
            'INSERT INTO households
             (territory_id, address, street_number, street_name, latitude, longitude,
              is_business, do_not_call, do_not_call_date, do_not_call_notes,
              do_not_call_private_notes, created_at, updated_at)
             VALUES
             (:territory_id, :address, :street_number, :street_name, :latitude, :longitude,
              :is_business, :do_not_call, :do_not_call_date, :do_not_call_notes,
              :do_not_call_private_notes, :now, :now)',
            array_merge($fields, ['now' => $now])
        );

        flash('success', 'Household created.');
        return $this->redirect('/households');
    }

    public function edit(int|string $id): Response
    {
        if ($redirect = $this->requireAuth()) {
            return $redirect;
        }

        $household = (new HouseholdRepository(database()))->find((int) $id);

        if (!$household) {
            return Response::notFound('Household not found.');
        }

        $territories = (new TerritoryRepository(database()))->allOrdered();

        return $this->view('households.edit', [
            'pageTitle'   => 'Edit Household',
            'household'   => $household,
            'territories' => $territories,
            'errors'      => [],
        ]);
    }

    public function update(int|string $id): Response
    {
        if ($redirect = $this->requireAuth()) {
            return $redirect;
        }

        $request   = Request::capture();
        $household = (new HouseholdRepository(database()))->find((int) $id);

        if (!$household) {
            return Response::notFound('Household not found.');
        }

        if (!verify_csrf_token($request->input('_token'))) {
            flash('error', 'Invalid request.');
            return $this->redirect('/households/' . $id . '/edit');
        }

        $fields    = $this->extractFields($request);
        $validator = (new Validator())
            ->required('territory_id', $fields['territory_id'], 'Territory is required.')
            ->required('address', $fields['address'], 'Address is required.')
            ->maxLength('address', $fields['address'], 255)
            ->optionalNumeric('street_number', $fields['street_number'])
            ->optionalNumeric('latitude', $fields['latitude'], 'Latitude must be a number.')
            ->optionalNumeric('longitude', $fields['longitude'], 'Longitude must be a number.');

        $territories = (new TerritoryRepository(database()))->allOrdered();

        if ($validator->fails()) {
            return $this->view('households.edit', [
                'pageTitle'   => 'Edit Household',
                'household'   => array_merge($household, $fields),
                'territories' => $territories,
                'errors'      => $validator->errors(),
            ]);
        }

        database()->execute(
            'UPDATE households SET
             territory_id = :territory_id, address = :address,
             street_number = :street_number, street_name = :street_name,
             latitude = :latitude, longitude = :longitude,
             is_business = :is_business, do_not_call = :do_not_call,
             do_not_call_date = :do_not_call_date, do_not_call_notes = :do_not_call_notes,
             do_not_call_private_notes = :do_not_call_private_notes,
             updated_at = :now
             WHERE id = :id',
            array_merge($fields, ['now' => date('Y-m-d H:i:s'), 'id' => $id])
        );

        flash('success', 'Household updated.');
        return $this->redirect('/households/' . $id);
    }

    public function delete(int|string $id): Response
    {
        if ($redirect = $this->requireAuth()) {
            return $redirect;
        }

        $request   = Request::capture();
        $household = (new HouseholdRepository(database()))->find((int) $id);

        if (!$household) {
            return Response::notFound('Household not found.');
        }

        if (!verify_csrf_token($request->input('_token'))) {
            flash('error', 'Invalid request.');
            return $this->redirect('/households');
        }

        database()->execute('DELETE FROM householder_names WHERE household_id = :id', ['id' => $id]);
        database()->execute('DELETE FROM households WHERE id = :id', ['id' => $id]);

        flash('success', 'Household deleted.');
        return $this->redirect('/households');
    }

    /** @return array<string,mixed> */
    private function extractFields(Request $request): array
    {
        $doNotCall = (bool) $request->input('do_not_call');

        return [
            'territory_id'             => (string) ($request->input('territory_id') ?? ''),
            'address'                  => trim((string) ($request->input('address') ?? '')),
            'street_number'            => $request->input('street_number') !== '' ? $request->input('street_number') : null,
            'street_name'              => trim((string) ($request->input('street_name') ?? '')),
            'latitude'                 => $request->input('latitude') !== '' ? $request->input('latitude') : null,
            'longitude'                => $request->input('longitude') !== '' ? $request->input('longitude') : null,
            'is_business'              => (int) (bool) $request->input('is_business'),
            'do_not_call'              => (int) $doNotCall,
            'do_not_call_date'         => $doNotCall ? (trim((string) ($request->input('do_not_call_date') ?? '')) ?: null) : null,
            'do_not_call_notes'        => trim((string) ($request->input('do_not_call_notes') ?? '')),
            'do_not_call_private_notes' => trim((string) ($request->input('do_not_call_private_notes') ?? '')),
        ];
    }
}
