<?php

namespace App\Http\Controllers\Admin;

use App\Enums\JournalStatus;
use App\Enums\PartyRoleType;
use App\Http\Controllers\Controller;
use App\Models\Account;
use App\Models\Branch;
use App\Models\JournalLine;
use App\Models\Party;
use App\Models\TaxCode;
use App\Services\Accounting\LedgerBalanceService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

final class PartyController extends Controller
{
    public function __construct(private readonly LedgerBalanceService $balances) {}

    public function index(Request $request): View
    {
        abort_unless($request->user()->hasPermission('parties.view'), 403);
        $query = Party::query()->with('roles')->where('company_id', $request->user()->company_id);
        if ($request->user()->branch_id && ! $request->user()->hasPermission('branches.view_all')) {
            $query->where(function ($query) use ($request): void {
                $query->where('assigned_branch_id', $request->user()->branch_id)->orWhereNull('assigned_branch_id');
            });
        }
        if ($search = trim((string) $request->query('search'))) {
            $query->where(function ($query) use ($search): void {
                $query->where('legal_name', 'like', "%{$search}%")
                    ->orWhere('trade_name', 'like', "%{$search}%")
                    ->orWhere('trn', 'like', "%{$search}%")
                    ->orWhere('phone', 'like', "%{$search}%")
                    ->orWhere('email', 'like', "%{$search}%");
            });
        }
        if ($role = $request->query('role')) {
            $query->whereHas('roles', fn ($query) => $query->where('role', $role));
        }
        $parties = $query->orderBy('legal_name')->paginate(25)->withQueryString();

        return view('parties.index', compact('parties'));
    }

    public function create(Request $request): View
    {
        abort_unless($request->user()->hasPermission('parties.create'), 403);
        return $this->form(new Party(), $request);
    }

    public function store(Request $request): RedirectResponse
    {
        abort_unless($request->user()->hasPermission('parties.create'), 403);
        $data = $this->validateData($request);
        $roles = $data['roles'];
        unset($data['roles']);
        $data['company_id'] = $request->user()->company_id;
        $data['assigned_branch_id'] ??= $request->user()->branch_id;

        $party = DB::transaction(function () use ($data, $roles): Party {
            $party = Party::query()->create($data);
            foreach ($roles as $role) {
                $party->roles()->create(['role' => $role]);
            }
            return $party;
        });

        return redirect()->route('parties.show', $party)->with('success', 'Party created successfully.');
    }

    public function show(Request $request, Party $party): View
    {
        $this->ensureAccess($request, $party, 'view');
        $party->load('roles');
        $position = $this->balances->combinedPartyPosition($party->id);
        $ledger = JournalLine::query()
            ->with(['journalEntry', 'account'])
            ->where('party_id', $party->id)
            ->whereHas('journalEntry', fn ($query) => $query->where('status', JournalStatus::POSTED->value))
            ->latest('id')->paginate(30);

        return view('parties.show', compact('party', 'position', 'ledger'));
    }

    public function edit(Request $request, Party $party): View
    {
        $this->ensureAccess($request, $party, 'edit_draft');
        return $this->form($party->load('roles'), $request);
    }

    public function update(Request $request, Party $party): RedirectResponse
    {
        $this->ensureAccess($request, $party, 'edit_draft');
        $data = $this->validateData($request, $party);
        $roles = $data['roles'];
        unset($data['roles']);

        DB::transaction(function () use ($party, $data, $roles): void {
            $party->update($data);
            $party->roles()->delete();
            foreach ($roles as $role) {
                $party->roles()->create(['role' => $role]);
            }
        });

        return redirect()->route('parties.show', $party)->with('success', 'Party updated successfully.');
    }

    public function destroy(Request $request, Party $party): RedirectResponse
    {
        $this->ensureAccess($request, $party, 'delete_draft');
        if (JournalLine::query()->where('party_id', $party->id)->exists()) {
            return back()->with('error', 'This party has accounting transactions and cannot be deleted. Deactivate it instead.');
        }
        $party->delete();
        return redirect()->route('parties.index')->with('success', 'Party deleted.');
    }

    private function form(Party $party, Request $request): View
    {
        $accounts = Account::query()->where('company_id', $request->user()->company_id)->where('is_active', true)->orderBy('code')->get();
        $taxCodes = TaxCode::query()->where('company_id', $request->user()->company_id)->where('is_active', true)->orderBy('code')->get();
        $branches = Branch::query()->where('company_id', $request->user()->company_id)->where('is_active', true)->orderBy('code')->get();
        $roleOptions = PartyRoleType::cases();

        return view('parties.form', compact('party', 'accounts', 'taxCodes', 'branches', 'roleOptions'));
    }

    private function validateData(Request $request, ?Party $party = null): array
    {
        return $request->validate([
            'legal_name' => ['required', 'string', 'max:200'],
            'trade_name' => ['nullable', 'string', 'max:200'],
            'entity_type' => ['required', Rule::in(['company', 'individual'])],
            'roles' => ['required', 'array', 'min:1'],
            'roles.*' => [Rule::enum(PartyRoleType::class)],
            'trn' => ['nullable', 'string', 'max:20'],
            'trade_license_number' => ['nullable', 'string', 'max:100'],
            'license_expiry_date' => ['nullable', 'date'],
            'identification_number' => ['nullable', 'string', 'max:100'],
            'contact_person' => ['nullable', 'string', 'max:150'],
            'phone' => ['nullable', 'string', 'max:50'],
            'email' => ['nullable', 'email', 'max:150'],
            'billing_address' => ['nullable', 'string', 'max:3000'],
            'operational_address' => ['nullable', 'string', 'max:3000'],
            'emirate' => ['nullable', 'string', 'max:100'],
            'country_code' => ['required', 'string', 'size:2'],
            'credit_limit' => ['nullable', 'numeric', 'min:0'],
            'credit_period_days' => ['nullable', 'integer', 'min:0'],
            'default_currency' => ['required', 'string', 'size:3'],
            'default_tax_code_id' => ['nullable', 'integer'],
            'default_income_account_id' => ['nullable', 'integer'],
            'default_expense_account_id' => ['nullable', 'integer'],
            'default_receivable_account_id' => ['nullable', 'integer'],
            'default_payable_account_id' => ['nullable', 'integer'],
            'assigned_branch_id' => ['nullable', 'integer'],
            'einvoice_address' => ['nullable', 'string', 'max:200'],
            'einvoice_scheme' => ['nullable', 'string', 'max:100'],
            'notes' => ['nullable', 'string'],
            'is_active' => ['nullable', 'boolean'],
        ]) + ['is_active' => $request->boolean('is_active')];
    }

    private function ensureAccess(Request $request, Party $party, string $action): void
    {
        abort_unless($request->user()->hasPermission('parties.'.$action), 403);
        abort_unless((int) $party->company_id === (int) $request->user()->company_id, 404);
        if ($request->user()->branch_id && ! $request->user()->hasPermission('branches.view_all')) {
            abort_unless($party->assigned_branch_id === null || (int) $party->assigned_branch_id === (int) $request->user()->branch_id, 403);
        }
    }
}
