<?php

namespace Database\Seeders;

use App\Models\Activity;
use App\Models\Company;
use App\Models\Contact;
use App\Models\Deal;
use App\Models\Pipeline;
use App\Models\Project;
use App\Models\Task;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Collection;

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;

    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $admin = User::factory()->create([
            'name' => 'CRM Admin',
            'email' => 'admin@crm.local',
            'role' => 'admin',
            'phone' => '+1-555-1000',
            'job_title' => 'CRM Administrator',
            'birth_date' => '1990-06-15',
        ]);

        $manager = User::factory()->create([
            'name' => 'Sales Manager',
            'email' => 'manager@crm.local',
            'role' => 'manager',
            'job_title' => 'Head of Sales',
            'manager_id' => $admin->id,
            'birth_date' => '1988-03-21',
        ]);

        $salesTeam = User::factory(4)->create([
            'role' => 'sales',
            'manager_id' => $manager->id,
        ]);

        $users = collect([$admin, $manager])->merge($salesTeam);

        $mainPipeline = Pipeline::create([
            'name' => 'Core Sales',
            'description' => 'Primary B2B funnel for new customers.',
            'creator_id' => $admin->id,
            'is_default' => true,
            'is_active' => true,
            'sort_order' => 0,
        ]);

        $renewalPipeline = Pipeline::create([
            'name' => 'Renewal and Upsell',
            'description' => 'Funnel for existing customers and contract expansion.',
            'creator_id' => $manager->id,
            'is_default' => false,
            'is_active' => true,
            'sort_order' => 1,
        ]);

        $mainStages = $this->seedStages($mainPipeline, [
            ['name' => 'New Lead', 'probability' => 10],
            ['name' => 'Qualification', 'probability' => 25],
            ['name' => 'Meeting', 'probability' => 45],
            ['name' => 'Proposal', 'probability' => 65],
            ['name' => 'Negotiation', 'probability' => 80],
            ['name' => 'Won', 'probability' => 100, 'is_won' => true],
            ['name' => 'Lost', 'probability' => 0, 'is_lost' => true],
        ]);

        $renewalStages = $this->seedStages($renewalPipeline, [
            ['name' => 'Planning', 'probability' => 20],
            ['name' => 'Budget Approval', 'probability' => 50],
            ['name' => 'Signing', 'probability' => 80],
            ['name' => 'Won', 'probability' => 100, 'is_won' => true],
            ['name' => 'Lost', 'probability' => 0, 'is_lost' => true],
        ]);

        $companies = Company::factory(14)
            ->sequence(fn ($sequence) => [
                'owner_id' => $users->random()->id,
                'status' => $sequence->index % 4 === 0 ? 'client' : 'lead',
            ])
            ->create();

        $contacts = collect();
        foreach ($companies as $company) {
            $batch = Contact::factory(random_int(1, 3))
                ->create([
                    'company_id' => $company->id,
                    'owner_id' => $company->owner_id,
                ]);

            $contacts = $contacts->merge($batch);
        }

        $this->seedDealsAndWorkload($mainPipeline->id, $mainStages, $companies, $contacts, $users, 24);
        $this->seedDealsAndWorkload($renewalPipeline->id, $renewalStages, $companies, $contacts, $users, 12);
        $this->seedProjectsAndBoards($companies, $contacts, $users);
    }

    /**
     * @param  array<int, array{name: string, probability: int, is_won?: bool, is_lost?: bool}>  $stageData
     * @return Collection<int, \App\Models\DealStage>
     */
    private function seedStages(Pipeline $pipeline, array $stageData): Collection
    {
        $created = collect();

        foreach ($stageData as $index => $data) {
            $created->push($pipeline->stages()->create([
                'name' => $data['name'],
                'code' => 'stage_'.($index + 1),
                'sort_order' => $index,
                'probability' => $data['probability'],
                'is_won' => $data['is_won'] ?? false,
                'is_lost' => $data['is_lost'] ?? false,
            ]));
        }

        return $created;
    }

    /**
     * @param  Collection<int, \App\Models\DealStage>  $stages
     * @param  Collection<int, Company>  $companies
     * @param  Collection<int, Contact>  $contacts
     * @param  Collection<int, User>  $users
     */
    private function seedDealsAndWorkload(int $pipelineId, Collection $stages, Collection $companies, Collection $contacts, Collection $users, int $count): void
    {
        $wonStage = $stages->firstWhere('is_won', true);
        $lostStage = $stages->firstWhere('is_lost', true);
        $openStages = $stages->reject(fn ($stage) => $stage->is_won || $stage->is_lost)->values();

        for ($i = 0; $i < $count; $i++) {
            $company = $companies->random();
            $contact = $contacts->where('company_id', $company->id)->random();
            $owner = $users->random();

            $status = fake()->randomElement(['open', 'open', 'open', 'won', 'lost']);
            $stage = $status === 'won'
                ? $wonStage
                : ($status === 'lost' ? $lostStage : $openStages->random());

            $deal = Deal::create([
                'title' => fake()->company().' · '.fake()->bs(),
                'pipeline_id' => $pipelineId,
                'stage_id' => $stage->id,
                'company_id' => $company->id,
                'contact_id' => $contact->id,
                'owner_id' => $owner->id,
                'amount' => fake()->randomFloat(2, 1000, 90000),
                'currency' => 'USD',
                'priority' => fake()->randomElement(['low', 'medium', 'high']),
                'status' => $status,
                'expected_close_at' => $status === 'open' ? fake()->dateTimeBetween('now', '+45 days') : null,
                'closed_at' => $status === 'open' ? null : fake()->dateTimeBetween('-30 days', 'now'),
                'last_activity_at' => fake()->dateTimeBetween('-10 days', 'now'),
                'source' => fake()->randomElement(['manual', 'website', 'ads', 'referral']),
                'lost_reason' => $status === 'lost' ? fake()->sentence() : null,
                'description' => fake()->optional()->paragraph(),
            ]);

            $tasks = Task::factory(random_int(1, 3))
                ->create([
                    'deal_id' => $deal->id,
                    'company_id' => $company->id,
                    'contact_id' => $contact->id,
                    'creator_id' => $owner->id,
                    'assignee_id' => $users->random()->id,
                    'status' => fake()->randomElement(['todo', 'in_progress', 'review', 'done']),
                ]);

            foreach ($tasks as $task) {
                if ($task->status === 'done' && ! $task->completed_at) {
                    $task->update(['completed_at' => now()->subDays(random_int(0, 7))]);
                }
            }

            Activity::factory(random_int(1, 4))
                ->create([
                    'deal_id' => $deal->id,
                    'company_id' => $company->id,
                    'contact_id' => $contact->id,
                    'user_id' => $users->random()->id,
                    'occurred_at' => fake()->dateTimeBetween('-20 days', 'now'),
                ]);
        }
    }

    /**
     * @param  Collection<int, Company>  $companies
     * @param  Collection<int, Contact>  $contacts
     * @param  Collection<int, User>  $users
     */
    private function seedProjectsAndBoards(Collection $companies, Collection $contacts, Collection $users): void
    {
        $projectStatuses = ['planned', 'active', 'active', 'active', 'on_hold', 'completed', 'cancelled'];
        $deals = Deal::query()->inRandomOrder()->limit(20)->get();

        for ($index = 1; $index <= 10; $index++) {
            $company = $companies->random();
            $owner = $users->random();
            $manager = $users->random();
            $status = fake()->randomElement($projectStatuses);
            $budget = fake()->randomFloat(2, 15000, 180000);

            $project = Project::create([
                'name' => 'Project '.$index.' · '.fake()->bs(),
                'code' => 'PRJ_'.str_pad((string) $index, 3, '0', STR_PAD_LEFT),
                'description' => fake()->optional()->paragraph(),
                'company_id' => $company->id,
                'deal_id' => $deals->isNotEmpty() && fake()->boolean(60) ? $deals->random()->id : null,
                'owner_id' => $owner->id,
                'manager_id' => $manager->id,
                'status' => $status,
                'priority' => fake()->randomElement(['low', 'medium', 'high', 'critical']),
                'health' => fake()->randomElement(['normal', 'warning', 'risk']),
                'progress' => 0,
                'budget' => $budget,
                'spent' => 0,
                'starts_at' => now()->subDays(random_int(10, 60)),
                'due_at' => now()->addDays(random_int(10, 120)),
                'completed_at' => $status === 'completed' ? now()->subDays(random_int(1, 20)) : null,
                'visibility' => fake()->randomElement(['team', 'public', 'private']),
                'notes' => fake()->optional()->sentence(),
            ]);

            $stageNames = ['Backlog', 'In Progress', 'Review', 'Done'];
            $stages = collect();
            foreach ($stageNames as $sort => $name) {
                $stages->push($project->stages()->create([
                    'name' => $name,
                    'code' => strtolower(str_replace(' ', '_', $name)).'_'.($sort + 1),
                    'sort_order' => $sort,
                    'is_done' => $name === 'Done',
                ]));
            }

            $doneStage = $stages->firstWhere('is_done', true);
            $activeStages = $stages->where('is_done', false)->values();

            $memberCount = min($users->count(), random_int(2, 4));
            $selectedMembers = $users->random($memberCount);
            $syncData = [];
            foreach ($selectedMembers as $member) {
                $role = 'contributor';
                if ($member->id === $manager->id) {
                    $role = 'manager';
                }
                if ($member->id === $owner->id) {
                    $role = 'owner';
                }

                $syncData[$member->id] = [
                    'role' => $role,
                    'joined_at' => now()->subDays(random_int(1, 30)),
                ];
            }
            $syncData[$owner->id] = ['role' => 'owner', 'joined_at' => now()->subDays(35)];
            $syncData[$manager->id] = ['role' => $manager->id === $owner->id ? 'owner' : 'manager', 'joined_at' => now()->subDays(32)];

            $project->members()->sync($syncData);

            $taskCount = random_int(4, 12);
            $trackedHours = 0.0;
            $createdTasks = collect();

            for ($taskSort = 0; $taskSort < $taskCount; $taskSort++) {
                $taskStatus = fake()->randomElement(['todo', 'in_progress', 'review', 'done']);
                $stage = $taskStatus === 'done' ? $doneStage : ($activeStages->isNotEmpty() ? $activeStages->random() : null);
                $assignee = $users->random();
                $contact = $contacts->where('company_id', $company->id)->random();
                $estimated = fake()->randomFloat(1, 2, 24);
                $tracked = min($estimated + fake()->randomFloat(1, 0, 8), 32.0);
                $startsAt = now()->subDays(random_int(0, 15));
                $dueAt = $startsAt->copy()->addDays(random_int(1, 45));
                $trackedHours += $tracked;

                $createdTasks->push(Task::create([
                    'title' => fake()->sentence(4),
                    'description' => fake()->optional()->sentence(),
                    'deal_id' => $project->deal_id,
                    'company_id' => $company->id,
                    'contact_id' => $contact->id,
                    'project_id' => $project->id,
                    'project_stage_id' => $stage?->id,
                    'creator_id' => $owner->id,
                    'assignee_id' => $assignee->id,
                    'status' => $taskStatus,
                    'priority' => fake()->randomElement(['low', 'medium', 'high', 'urgent']),
                    'estimated_hours' => $estimated,
                    'tracked_hours' => $tracked,
                    'sort_order' => $taskSort,
                    'starts_at' => $startsAt,
                    'due_at' => $dueAt,
                    'reminder_at' => fake()->optional()->dateTimeBetween('-1 day', '+7 days'),
                    'completed_at' => $taskStatus === 'done' ? now()->subDays(random_int(0, 10)) : null,
                ]));
            }

            $parentCandidates = $createdTasks->shuffle()->take(random_int(1, min(3, max(1, $createdTasks->count()))));
            foreach ($parentCandidates as $parentTask) {
                $subtaskCount = random_int(1, 3);

                for ($subIndex = 0; $subIndex < $subtaskCount; $subIndex++) {
                    $subStatus = fake()->randomElement(['todo', 'in_progress', 'review', 'done']);
                    $subStage = $subStatus === 'done' ? $doneStage : ($activeStages->isNotEmpty() ? $activeStages->random() : null);
                    $subStart = now()->subDays(random_int(0, 10));
                    $subDue = $subStart->copy()->addDays(random_int(1, 30));

                    Task::create([
                        'title' => 'Subtask · '.fake()->sentence(3),
                        'description' => fake()->optional()->sentence(),
                        'deal_id' => $project->deal_id,
                        'company_id' => $company->id,
                        'contact_id' => $contact->id,
                        'project_id' => $project->id,
                        'project_stage_id' => $subStage?->id,
                        'parent_id' => $parentTask->id,
                        'creator_id' => $owner->id,
                        'assignee_id' => $users->random()->id,
                        'status' => $subStatus,
                        'priority' => fake()->randomElement(['low', 'medium', 'high', 'urgent']),
                        'estimated_hours' => fake()->randomFloat(1, 1, 10),
                        'tracked_hours' => fake()->randomFloat(1, 0, 8),
                        'sort_order' => $subIndex,
                        'starts_at' => $subStart,
                        'due_at' => $subDue,
                        'reminder_at' => fake()->optional()->dateTimeBetween('-1 day', '+7 days'),
                        'completed_at' => $subStatus === 'done' ? now()->subDays(random_int(0, 7)) : null,
                    ]);
                }
            }

            $project->recalculateProgress();
            $project->updateQuietly([
                'spent' => min($budget, $trackedHours * fake()->randomFloat(2, 35, 120)),
            ]);
        }
    }
}
