<?php

declare(strict_types=1);

namespace ImovelZapAi\Owners\Presentation\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use ImovelZapAi\Owners\Application\Actions\ChangeListingStageAction;
use ImovelZapAi\Owners\Application\Actions\CreateListingAgreementAction;
use ImovelZapAi\Owners\Application\Actions\CreateListingOpportunityAction;
use ImovelZapAi\Owners\Application\Actions\CreateOwnerAction;
use ImovelZapAi\Owners\Application\Actions\LinkOwnerPropertyAction;
use ImovelZapAi\Owners\Application\Actions\RunOwnerAlertsAction;
use ImovelZapAi\Owners\Application\Actions\ScheduleListingVisitAction;
use ImovelZapAi\Owners\Application\Actions\UpdateListingEvaluationAction;
use ImovelZapAi\Owners\Application\Queries\GetListingKanbanQuery;
use ImovelZapAi\Owners\Application\Queries\GetOwnerDashboardQuery;
use ImovelZapAi\Owners\Application\Queries\ListListingOpportunitiesQuery;
use ImovelZapAi\Owners\Application\Queries\ListOwnersQuery;
use ImovelZapAi\Owners\Application\Services\DefaultListingPipelineSeeder;
use ImovelZapAi\Owners\Domain\Enums\ListingAgreementType;
use ImovelZapAi\Owners\Domain\Enums\OwnerType;
use ImovelZapAi\Owners\Domain\Exceptions\OwnerException;
use ImovelZapAi\Owners\Infrastructure\Persistence\Eloquent\ListingOpportunityModel;
use ImovelZapAi\Owners\Infrastructure\Persistence\Eloquent\OwnerModel;
use ImovelZapAi\Tenancy\Support\TenantContext;
use ImovelZapAi\Visits\Domain\Enums\VisitPurpose;

final class OwnerController extends Controller
{
    public function index(Request $request, ListOwnersQuery $query): JsonResponse
    {
        return response()->json($query->execute((string) TenantContext::id(), [
            'search' => $request->query('search'),
            'per_page' => (int) $request->query('per_page', 20),
        ]));
    }

    public function store(Request $request, CreateOwnerAction $action): JsonResponse
    {
        $data = $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'type' => ['nullable', 'in:person,company'],
            'document' => ['nullable', 'string', 'max:32'],
            'trade_name' => ['nullable', 'string', 'max:255'],
            'notes' => ['nullable', 'string'],
            'preferred_contact_channel' => ['nullable', 'in:phone,email,whatsapp'],
            'contacts' => ['nullable', 'array'],
            'contacts.*.channel' => ['required_with:contacts', 'in:phone,email,whatsapp'],
            'contacts.*.value' => ['required_with:contacts', 'string'],
            'contacts.*.is_primary' => ['nullable', 'boolean'],
            'addresses' => ['nullable', 'array'],
            'documents' => ['nullable', 'array'],
        ]);

        $owner = $action->execute((string) TenantContext::id(), $request->user(), $data);

        return response()->json($this->serializeOwner($owner), 201);
    }

    public function show(string $id): JsonResponse
    {
        $owner = OwnerModel::query()
            ->where('tenant_id', TenantContext::id())
            ->whereKey($id)
            ->with(['contacts', 'addresses', 'documents', 'properties', 'opportunities.stage', 'agreements'])
            ->firstOrFail();

        return response()->json($this->serializeOwner($owner));
    }

    public function storeOpportunity(Request $request, CreateListingOpportunityAction $action): JsonResponse
    {
        $data = $request->validate([
            'owner_id' => ['required', 'uuid', 'exists:owners,id'],
            'title' => ['nullable', 'string', 'max:255'],
            'property_id' => ['nullable', 'uuid', 'exists:properties,id'],
            'stage_key' => ['nullable', 'string'],
            'owner_asking_price' => ['nullable', 'numeric'],
            'agency_recommended_price' => ['nullable', 'numeric'],
            'listed_price' => ['nullable', 'numeric'],
            'conservation_state' => ['nullable', 'string'],
            'pending_documents' => ['nullable', 'string'],
            'sale_potential' => ['nullable', 'string'],
            'evaluation_notes' => ['nullable', 'string'],
            'source' => ['nullable', 'string'],
        ]);

        $opp = $action->execute((string) TenantContext::id(), $request->user(), $data);

        return response()->json($this->serializeOpportunity($opp), 201);
    }

    public function opportunities(Request $request, ListListingOpportunitiesQuery $query): JsonResponse
    {
        return response()->json($query->execute((string) TenantContext::id(), [
            'stage_key' => $request->query('stage_key'),
            'owner_id' => $request->query('owner_id'),
            'open_only' => filter_var($request->query('open_only', false), FILTER_VALIDATE_BOOL),
            'per_page' => (int) $request->query('per_page', 20),
        ]));
    }

    public function changeStage(string $id, Request $request, ChangeListingStageAction $action): JsonResponse
    {
        $data = $request->validate([
            'stage_key' => ['required', 'string'],
            'lost_reason' => ['nullable', 'string'],
        ]);

        try {
            $opp = $action->execute($this->findOpportunity($id), $request->user(), $data['stage_key'], $data['lost_reason'] ?? null);
        } catch (OwnerException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json($this->serializeOpportunity($opp));
    }

    public function updateEvaluation(string $id, Request $request, UpdateListingEvaluationAction $action): JsonResponse
    {
        $data = $request->validate([
            'owner_asking_price' => ['nullable', 'numeric'],
            'agency_recommended_price' => ['nullable', 'numeric'],
            'listed_price' => ['nullable', 'numeric'],
            'conservation_state' => ['nullable', 'string'],
            'pending_documents' => ['nullable', 'string'],
            'sale_potential' => ['nullable', 'string'],
            'evaluation_notes' => ['nullable', 'string'],
        ]);

        try {
            $opp = $action->execute($this->findOpportunity($id), $request->user(), $data);
        } catch (OwnerException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json($this->serializeOpportunity($opp));
    }

    public function storeAgreement(Request $request, CreateListingAgreementAction $action): JsonResponse
    {
        $data = $request->validate([
            'owner_id' => ['required', 'uuid', 'exists:owners,id'],
            'property_id' => ['nullable', 'uuid', 'exists:properties,id'],
            'listing_opportunity_id' => ['nullable', 'uuid', 'exists:listing_opportunities,id'],
            'type' => ['required', 'in:'.implode(',', array_map(fn ($c) => $c->value, ListingAgreementType::cases()))],
            'starts_on' => ['required', 'date'],
            'ends_on' => ['required', 'date', 'after:starts_on'],
            'commission_percent' => ['nullable', 'numeric'],
            'auto_renew' => ['nullable', 'boolean'],
            'notes' => ['nullable', 'string'],
            'activate' => ['nullable', 'boolean'],
        ]);

        $agreement = $action->execute((string) TenantContext::id(), $request->user(), $data);

        return response()->json([
            'id' => $agreement->id,
            'type' => $agreement->type->value ?? $agreement->type,
            'status' => $agreement->status->value ?? $agreement->status,
            'starts_on' => $agreement->starts_on?->toDateString(),
            'ends_on' => $agreement->ends_on?->toDateString(),
            'commission_percent' => $agreement->commission_percent,
        ], 201);
    }

    public function scheduleVisit(string $id, Request $request, ScheduleListingVisitAction $action): JsonResponse
    {
        $data = $request->validate([
            'scheduled_start' => ['required', 'date'],
            'scheduled_end' => ['nullable', 'date'],
            'purpose' => ['nullable', 'in:'.implode(',', VisitPurpose::values())],
            'location_notes' => ['nullable', 'string'],
        ]);

        try {
            $link = $action->execute($this->findOpportunity($id), $request->user(), $data);
        } catch (OwnerException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json([
            'id' => $link->id,
            'visit_id' => $link->visit_id,
            'purpose' => $link->purpose->value ?? $link->purpose,
        ], 201);
    }

    public function linkProperty(Request $request, LinkOwnerPropertyAction $action): JsonResponse
    {
        $data = $request->validate([
            'owner_id' => ['required', 'uuid', 'exists:owners,id'],
            'property_id' => ['required', 'uuid', 'exists:properties,id'],
            'listing_opportunity_id' => ['nullable', 'uuid'],
            'is_primary' => ['nullable', 'boolean'],
            'ownership_share' => ['nullable', 'numeric'],
        ]);

        try {
            $action->execute(
                (string) TenantContext::id(),
                $request->user(),
                $data['owner_id'],
                $data['property_id'],
                (bool) ($data['is_primary'] ?? true),
                isset($data['ownership_share']) ? (float) $data['ownership_share'] : null,
                $data['listing_opportunity_id'] ?? null,
            );
        } catch (OwnerException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json(['ok' => true]);
    }

    public function kanban(GetListingKanbanQuery $query): JsonResponse
    {
        return response()->json($query->execute((string) TenantContext::id()));
    }

    public function dashboard(GetOwnerDashboardQuery $query): JsonResponse
    {
        return response()->json($query->execute((string) TenantContext::id()));
    }

    public function pipeline(DefaultListingPipelineSeeder $seeder): JsonResponse
    {
        $pipeline = $seeder->ensureForTenant((string) TenantContext::id());

        return response()->json([
            'id' => $pipeline->id,
            'name' => $pipeline->name,
            'stages' => $pipeline->stages->map(fn ($s) => [
                'id' => $s->id,
                'key' => $s->key instanceof \BackedEnum ? $s->key->value : $s->key,
                'name' => $s->name,
                'position' => $s->position,
            ])->values(),
        ]);
    }

    public function runAlerts(Request $request, RunOwnerAlertsAction $action): JsonResponse
    {
        return response()->json($action->execute((string) TenantContext::id(), $request->user()));
    }

    private function findOpportunity(string $id): ListingOpportunityModel
    {
        return ListingOpportunityModel::query()
            ->where('tenant_id', TenantContext::id())
            ->whereKey($id)
            ->firstOrFail();
    }

    /** @return array<string, mixed> */
    private function serializeOwner(OwnerModel $owner): array
    {
        return [
            'id' => $owner->id,
            'type' => $owner->type instanceof OwnerType ? $owner->type->value : $owner->type,
            'name' => $owner->name,
            'document' => $owner->document,
            'trade_name' => $owner->trade_name,
            'notes' => $owner->notes,
            'contacts' => $owner->relationLoaded('contacts') ? $owner->contacts : null,
            'addresses' => $owner->relationLoaded('addresses') ? $owner->addresses : null,
            'created_at' => $owner->created_at?->toIso8601String(),
        ];
    }

    /** @return array<string, mixed> */
    private function serializeOpportunity(ListingOpportunityModel $opp): array
    {
        return [
            'id' => $opp->id,
            'title' => $opp->title,
            'owner_id' => $opp->owner_id,
            'property_id' => $opp->property_id,
            'stage_key' => $opp->stage?->key instanceof \BackedEnum ? $opp->stage->key->value : $opp->stage?->key,
            'stage_name' => $opp->stage?->name,
            'owner_asking_price' => $opp->owner_asking_price,
            'agency_recommended_price' => $opp->agency_recommended_price,
            'listed_price' => $opp->listed_price,
            'conservation_state' => $opp->conservation_state,
            'sale_potential' => $opp->sale_potential,
            'closed_at' => $opp->closed_at?->toIso8601String(),
        ];
    }
}
