<?php

namespace App\Services\Auth;

use App\Models\Tenant;
use App\Models\TenantMembership;
use App\Models\User;
use App\Services\Intune\AuditLogger;
use App\Support\Audit\AuditActionId;
use DomainException;
use Illuminate\Support\Facades\DB;

class TenantMembershipManager
{
    public function __construct(public AuditLogger $auditLogger) {}

    public function addMember(
        Tenant $tenant,
        User $actor,
        User $member,
        string $role,
        string $source = 'manual',
        ?string $sourceRef = null,
    ): TenantMembership {
        $this->assertValidRole($role);

        return DB::transaction(function () use ($tenant, $actor, $member, $role, $source, $sourceRef): TenantMembership {
            $existing = TenantMembership::query()
                ->where('tenant_id', $tenant->getKey())
                ->where('user_id', $member->getKey())
                ->first();

            if ($existing) {
                if ($existing->role !== $role) {
                    $existing->forceFill([
                        'role' => $role,
                        'source' => $source,
                        'source_ref' => $sourceRef,
                        'created_by_user_id' => (int) $actor->getKey(),
                    ])->save();

                    $this->auditLogger->log(
                        tenant: $tenant,
                        action: AuditActionId::TenantMembershipRoleChange->value,
                        context: [
                            'metadata' => [
                                'member_user_id' => (int) $member->getKey(),
                                'from_role' => $existing->getOriginal('role'),
                                'to_role' => $role,
                                'source' => $source,
                            ],
                        ],
                        actorId: (int) $actor->getKey(),
                        actorEmail: $actor->email,
                        actorName: $actor->name,
                        status: 'success',
                        resourceType: 'tenant',
                        resourceId: (string) $tenant->getKey(),
                    );
                }

                return $existing->refresh();
            }

            $membership = TenantMembership::query()->create([
                'tenant_id' => (int) $tenant->getKey(),
                'user_id' => (int) $member->getKey(),
                'role' => $role,
                'source' => $source,
                'source_ref' => $sourceRef,
                'created_by_user_id' => (int) $actor->getKey(),
            ]);

            $this->auditLogger->log(
                tenant: $tenant,
                action: AuditActionId::TenantMembershipAdd->value,
                context: [
                    'metadata' => [
                        'member_user_id' => (int) $member->getKey(),
                        'role' => $role,
                        'source' => $source,
                    ],
                ],
                actorId: (int) $actor->getKey(),
                actorEmail: $actor->email,
                actorName: $actor->name,
                status: 'success',
                resourceType: 'tenant',
                resourceId: (string) $tenant->getKey(),
            );

            return $membership;
        });
    }

    public function changeRole(Tenant $tenant, User $actor, TenantMembership $membership, string $newRole): TenantMembership
    {
        $this->assertValidRole($newRole);

        try {
            return DB::transaction(function () use ($tenant, $actor, $membership, $newRole): TenantMembership {
                $membership->refresh();

                if ($membership->tenant_id !== (int) $tenant->getKey()) {
                    throw new DomainException('Membership belongs to a different tenant.');
                }

                $oldRole = $membership->role;

                if ($oldRole === $newRole) {
                    return $membership;
                }

                $this->guardLastOwnerDemotion($tenant, $membership, $newRole);

                $membership->forceFill([
                    'role' => $newRole,
                ])->save();

                $this->auditLogger->log(
                    tenant: $tenant,
                    action: AuditActionId::TenantMembershipRoleChange->value,
                    context: [
                        'metadata' => [
                            'member_user_id' => (int) $membership->user_id,
                            'from_role' => $oldRole,
                            'to_role' => $newRole,
                        ],
                    ],
                    actorId: (int) $actor->getKey(),
                    actorEmail: $actor->email,
                    actorName: $actor->name,
                    status: 'success',
                    resourceType: 'tenant',
                    resourceId: (string) $tenant->getKey(),
                );

                return $membership->refresh();
            });
        } catch (DomainException $exception) {
            if ($exception->getMessage() === 'You cannot demote the last remaining owner.') {
                $this->auditLogger->log(
                    tenant: $tenant,
                    action: AuditActionId::TenantMembershipLastOwnerBlocked->value,
                    context: [
                        'metadata' => [
                            'member_user_id' => (int) $membership->user_id,
                            'from_role' => (string) $membership->role,
                            'attempted_to_role' => $newRole,
                        ],
                    ],
                    actorId: (int) $actor->getKey(),
                    actorEmail: $actor->email,
                    actorName: $actor->name,
                    status: 'blocked',
                    resourceType: 'tenant',
                    resourceId: (string) $tenant->getKey(),
                );
            }

            throw $exception;
        }
    }

    public function removeMember(Tenant $tenant, User $actor, TenantMembership $membership): void
    {
        try {
            DB::transaction(function () use ($tenant, $actor, $membership): void {
                $membership->refresh();

                if ($membership->tenant_id !== (int) $tenant->getKey()) {
                    throw new DomainException('Membership belongs to a different tenant.');
                }

                $this->guardLastOwnerRemoval($tenant, $membership);

                $memberUserId = (int) $membership->user_id;
                $oldRole = (string) $membership->role;

                $membership->delete();

                $this->auditLogger->log(
                    tenant: $tenant,
                    action: AuditActionId::TenantMembershipRemove->value,
                    context: [
                        'metadata' => [
                            'member_user_id' => $memberUserId,
                            'role' => $oldRole,
                        ],
                    ],
                    actorId: (int) $actor->getKey(),
                    actorEmail: $actor->email,
                    actorName: $actor->name,
                    status: 'success',
                    resourceType: 'tenant',
                    resourceId: (string) $tenant->getKey(),
                );
            });
        } catch (DomainException $exception) {
            if ($exception->getMessage() === 'You cannot remove the last remaining owner.') {
                $this->auditLogger->log(
                    tenant: $tenant,
                    action: AuditActionId::TenantMembershipLastOwnerBlocked->value,
                    context: [
                        'metadata' => [
                            'member_user_id' => (int) $membership->user_id,
                            'role' => (string) $membership->role,
                            'attempted_action' => 'remove',
                        ],
                    ],
                    actorId: (int) $actor->getKey(),
                    actorEmail: $actor->email,
                    actorName: $actor->name,
                    status: 'blocked',
                    resourceType: 'tenant',
                    resourceId: (string) $tenant->getKey(),
                );
            }

            throw $exception;
        }
    }

    public function bootstrapRecover(Tenant $tenant, User $actor, User $member): TenantMembership
    {
        $membership = $this->addMember(
            tenant: $tenant,
            actor: $actor,
            member: $member,
            role: 'owner',
            source: 'break_glass',
        );

        $this->auditLogger->log(
            tenant: $tenant,
            action: AuditActionId::TenantMembershipBootstrapRecover->value,
            context: [
                'metadata' => [
                    'member_user_id' => (int) $member->getKey(),
                ],
            ],
            actorId: (int) $actor->getKey(),
            actorEmail: $actor->email,
            actorName: $actor->name,
            status: 'success',
            resourceType: 'tenant',
            resourceId: (string) $tenant->getKey(),
        );

        return $membership;
    }

    private function guardLastOwnerRemoval(Tenant $tenant, TenantMembership $membership): void
    {
        if ($membership->role !== 'owner') {
            return;
        }

        $owners = TenantMembership::query()
            ->where('tenant_id', (int) $tenant->getKey())
            ->where('role', 'owner')
            ->count();

        if ($owners <= 1) {
            throw new DomainException('You cannot remove the last remaining owner.');
        }
    }

    private function guardLastOwnerDemotion(Tenant $tenant, TenantMembership $membership, string $newRole): void
    {
        if ($membership->role !== 'owner') {
            return;
        }

        if ($newRole === 'owner') {
            return;
        }

        $owners = TenantMembership::query()
            ->where('tenant_id', (int) $tenant->getKey())
            ->where('role', 'owner')
            ->count();

        if ($owners <= 1) {
            throw new DomainException('You cannot demote the last remaining owner.');
        }
    }

    private function assertValidRole(string $role): void
    {
        if (! in_array($role, ['owner', 'manager', 'operator', 'readonly'], true)) {
            throw new DomainException('Invalid role value.');
        }
    }
}
