<?php

namespace App\Services\Api;

use App\Models\ApiClient;
use App\Models\Victim;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class ApiClientScopeService
{
    public function applyToIncidentQuery($query, ApiClient $client, string $incidentAlias = 'incident_data', ?string $victimAlias = null)
    {
        if ($this->hasNationalScope($client)) {
            return $query;
        }

        return $query->where(function ($scopeQuery) use ($client, $incidentAlias, $victimAlias) {
            $applied = false;

            if ($client->hasDataScope('district')) {
                $districts = $this->districtIds($client);
                if (! empty($districts)) {
                    $scopeQuery->whereIn($incidentAlias . '.districtid', $districts);
                    $applied = true;
                }
            }

            if ($client->hasDataScope('partner')) {
                $partnerId = $client->partner_id ?: null;
                if ($partnerId) {
                    $method = $applied ? 'orWhere' : 'where';
                    $scopeQuery->{$method}(function ($q) use ($incidentAlias, $victimAlias, $partnerId) {
                        $q->whereExists(function ($sub) use ($incidentAlias, $partnerId) {
                            $sub->select(DB::raw(1))
                                ->from('users as api_scope_incident_users')
                                ->whereColumn('api_scope_incident_users.id', $incidentAlias . '.created_by')
                                ->where(function ($p) use ($partnerId) {
                                    $p->where('api_scope_incident_users.partnerid', $partnerId)
                                      ->orWhere('api_scope_incident_users.partner', $partnerId);
                                });
                        });

                        if ($victimAlias) {
                            $q->orWhereExists(function ($sub) use ($victimAlias, $partnerId) {
                                $sub->select(DB::raw(1))
                                    ->from('users as api_scope_victim_users')
                                    ->whereColumn('api_scope_victim_users.id', $victimAlias . '.created_by')
                                    ->where(function ($p) use ($partnerId) {
                                        $p->where('api_scope_victim_users.partnerid', $partnerId)
                                          ->orWhere('api_scope_victim_users.partner', $partnerId);
                                    });
                            });
                        } else {
                            $q->orWhereExists(function ($sub) use ($incidentAlias, $partnerId) {
                                $sub->select(DB::raw(1))
                                    ->from('victim as api_scope_victim_partner')
                                    ->join('users as api_scope_victim_users', 'api_scope_victim_partner.created_by', '=', 'api_scope_victim_users.id')
                                    ->whereColumn('api_scope_victim_partner.id', $incidentAlias . '.victimid')
                                    ->where(function ($p) use ($partnerId) {
                                        $p->where('api_scope_victim_users.partnerid', $partnerId)
                                          ->orWhere('api_scope_victim_users.partner', $partnerId);
                                    });
                            });
                        }
                    });
                    $applied = true;
                }
            }

            if ($client->hasDataScope('own_records')) {
                $ownerId = $client->created_by ?: null;
                if ($ownerId) {
                    $method = $applied ? 'orWhere' : 'where';
                    $scopeQuery->{$method}(function ($q) use ($incidentAlias, $victimAlias, $ownerId) {
                        $q->where($incidentAlias . '.created_by', $ownerId);

                        if ($victimAlias) {
                            $q->orWhere($victimAlias . '.created_by', $ownerId);
                        } else {
                            $q->orWhereExists(function ($sub) use ($incidentAlias, $ownerId) {
                                $sub->select(DB::raw(1))
                                    ->from('victim as api_scope_victim_own')
                                    ->whereColumn('api_scope_victim_own.id', $incidentAlias . '.victimid')
                                    ->where('api_scope_victim_own.created_by', $ownerId);
                            });
                        }
                    });
                    $applied = true;
                }
            }

            // Zero trust default: no explicit allowed scope means no data.
            if (! $applied) {
                $scopeQuery->whereRaw('1 = 0');
            }
        });
    }

    public function applyToVictimQuery($query, ApiClient $client, string $victimAlias = 'victim')
    {
        if ($this->hasNationalScope($client)) {
            return $query;
        }

        return $query->where(function ($scopeQuery) use ($client, $victimAlias) {
            $applied = false;

            if ($client->hasDataScope('district')) {
                $districts = $this->districtIds($client);
                if (! empty($districts)) {
                    $scopeQuery->where(function ($q) use ($victimAlias, $districts) {
                        $q->whereIn($victimAlias . '.districtid', $districts)
                          ->orWhereExists(function ($sub) use ($victimAlias, $districts) {
                              $sub->select(DB::raw(1))
                                  ->from('incident_data as api_scope_incidents')
                                  ->whereColumn('api_scope_incidents.victimid', $victimAlias . '.id')
                                  ->whereIn('api_scope_incidents.districtid', $districts);
                          });
                    });
                    $applied = true;
                }
            }

            if ($client->hasDataScope('partner')) {
                $partnerId = $client->partner_id ?: null;
                if ($partnerId) {
                    $method = $applied ? 'orWhereExists' : 'whereExists';
                    $scopeQuery->{$method}(function ($sub) use ($victimAlias, $partnerId) {
                        $sub->select(DB::raw(1))
                            ->from('users as api_scope_users')
                            ->whereColumn('api_scope_users.id', $victimAlias . '.created_by')
                            ->where(function ($p) use ($partnerId) {
                                $p->where('api_scope_users.partnerid', $partnerId)
                                  ->orWhere('api_scope_users.partner', $partnerId);
                            });
                    });
                    $applied = true;
                }
            }

            if ($client->hasDataScope('own_records')) {
                $ownerId = $client->created_by ?: null;
                if ($ownerId) {
                    $method = $applied ? 'orWhere' : 'where';
                    $scopeQuery->{$method}($victimAlias . '.created_by', $ownerId);
                    $applied = true;
                }
            }

            if (! $applied) {
                $scopeQuery->whereRaw('1 = 0');
            }
        });
    }

    public function assertCanAccessVictim(ApiClient $client, Victim $victim): void
    {
        $query = DB::table('victim')->where('victim.id', $victim->id);
        $this->applyToVictimQuery($query, $client, 'victim');

        if (! $query->exists()) {
            throw ValidationException::withMessages([
                'record' => 'API client is not allowed to access this case record based on its assigned data scope.',
            ]);
        }
    }

    public function allowedDistrictIds(ApiClient $client): array
    {
        return $this->districtIds($client);
    }

    private function hasNationalScope(ApiClient $client): bool
    {
        return $client->hasDataScope('national') || $client->canUseScope('*');
    }

    private function districtIds(ApiClient $client): array
    {
        return array_values(array_unique(array_filter(array_map(function ($id) {
            return (string) $id;
        }, $client->districtIdList()), function ($id) {
            return $id !== '' && $id !== '*';
        })));
    }
}
