<?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\TenantRole;
use DomainException;
use Illuminate\Support\Facades\DB;

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

    public function addMember(
        Tenant $tenant,
        User $actor,
        User $member,
        TenantRole $role,
        string $source = 'manual',
        ?string $sourceRef = null,
    ): TenantMembership {
        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->value) {
                    $existing->forceFill([
                        'role' => $role->value,
                        'source' => $source,
                        'source_ref' => $sourceRef,
                        'created_by_user_id' => (int) $actor->getKey(),
                    ])->save();

                    $this->auditLogger->log(
                        tenant: $tenant,
                        action: 'tenant_membership.role_change',
                        context: [
                            'metadata' => [
                                'member_user_id' => (int) $member->getKey(),
                                'from_role' => $existing->getOriginal('role'),
                                'to_role' => $role->value,
                                '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->value,
                'source' => $source,
                'source_ref' => $sourceRef,
                'created_by_user_id' => (int) $actor->getKey(),
            ]);

            $this->auditLogger->log(
                tenant: $tenant,
                action: 'tenant_membership.add',
                context: [
                    'metadata' => [
                        'member_user_id' => (int) $member->getKey(),
                        'role' => $role->value,
                        '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, TenantRole $newRole): TenantMembership
    {
        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->value) {
                return $membership;
            }

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

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

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

            return $membership->refresh();
        });
    }

    public function removeMember(Tenant $tenant, User $actor, TenantMembership $membership): void
    {
        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: 'tenant_membership.remove',
                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(),
            );
        });
    }

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

        $this->auditLogger->log(
            tenant: $tenant,
            action: 'tenant_membership.bootstrap_recover',
            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 !== TenantRole::Owner->value) {
            return;
        }

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

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

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

        if ($newRole === TenantRole::Owner) {
            return;
        }

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

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