<?php

namespace Tests\Feature\Api;

use App\Models\Feature;
use App\Models\FeatureType;
use App\Models\Inspection;
use App\Models\InspectionTemplate;
use App\Models\Issue;
use App\Models\IssueComment;
use App\Models\IssueTask;
use App\Models\Layer;
use App\Models\Media;
use App\Models\Phase;
use App\Models\ProgressUpdate;
use App\Models\Project;
use App\Models\SyncLog;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Laravel\Sanctum\Sanctum;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

class MobileApiTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        foreach (['update progress', 'manage all', 'create inspections', 'view issues', 'create issues', 'edit issues', 'upload media'] as $p) {
            Permission::findOrCreate($p);
        }
    }

    private function makeProject(?User $member = null): Project
    {
        $project = Project::create([
            'reference' => 'TEST-1',
            'name' => 'Proyecto Test',
            'address' => 'Calle Falsa 123',
            'lat' => 40.0,
            'lng' => -3.0,
            'start_date' => now()->toDateString(),
            'end_date_estimated' => now()->addMonths(6)->toDateString(),
            'status' => 'in_progress',
            'created_by' => $member?->id ?? User::factory()->create()->id,
        ]);

        if ($member) {
            $project->users()->attach($member->id, ['role_in_project' => 'supervisor']);
        }

        return $project;
    }

    private function makePhase(Project $project, string $name = 'Fase 1'): Phase
    {
        return Phase::create([
            'project_id' => $project->id,
            'name' => $name,
            'order' => 1,
            'color' => '#3b82f6',
            'progress_percent' => 0,
        ]);
    }

    private function makeLayer(Phase $phase): Layer
    {
        return Layer::create([
            'project_id' => $phase->project_id,
            'phase_id' => $phase->id,
            'name' => 'Capa 1',
            'color' => '#10b981',
            'uploaded_by' => $phase->project->created_by,
        ]);
    }

    private function makeFeature(Layer $layer): Feature
    {
        return Feature::create([
            'layer_id' => $layer->id,
            'name' => 'Elemento 1',
            'geometry' => ['type' => 'Point', 'coordinates' => [-3.0, 40.0]],
            'progress' => 0,
            'status' => 'planned',
        ]);
    }

    // ── Auth ───────────────────────────────────────────────────────────────────

    public function test_login_returns_a_token(): void
    {
        $user = User::factory()->create();

        $res = $this->postJson('/api/v1/login', [
            'email' => $user->email,
            'password' => 'password',
            'device_name' => 'Pixel-Test',
        ]);

        $res->assertOk()->assertJsonStructure(['token', 'user' => ['id', 'email', 'permissions']]);
        $this->assertDatabaseHas('devices', ['user_id' => $user->id, 'name' => 'Pixel-Test']);
    }

    public function test_login_fails_with_wrong_password(): void
    {
        $user = User::factory()->create();

        $this->postJson('/api/v1/login', [
            'email' => $user->email,
            'password' => 'incorrecta',
            'device_name' => 'Pixel-Test',
        ])->assertStatus(422);
    }

    public function test_me_requires_authentication(): void
    {
        $this->getJson('/api/v1/me')->assertStatus(401);
    }

    public function test_me_returns_user_with_token(): void
    {
        $user = User::factory()->create();
        Sanctum::actingAs($user, ['mobile-sync']);

        $this->getJson('/api/v1/me')
            ->assertOk()
            ->assertJsonPath('user.id', $user->id);
    }

    // ── Pull ─────────────────────────────────────────────────────────────────────

    public function test_projects_index_only_returns_accessible_projects(): void
    {
        $user = User::factory()->create();
        $mine = $this->makeProject($user);
        $other = $this->makeProject();          // not a member

        Sanctum::actingAs($user, ['mobile-sync']);

        $res = $this->getJson('/api/v1/projects')->assertOk();
        $ids = collect($res->json('projects'))->pluck('id');
        $this->assertTrue($ids->contains($mine->id));
        $this->assertFalse($ids->contains($other->id));
    }

    public function test_bundle_is_forbidden_for_non_member(): void
    {
        $user = User::factory()->create();
        $project = $this->makeProject();        // user is not a member

        Sanctum::actingAs($user, ['mobile-sync']);

        $this->getJson("/api/v1/projects/{$project->id}/bundle")->assertStatus(403);
    }

    public function test_bundle_returns_structure_for_member(): void
    {
        $user = User::factory()->create();
        $project = $this->makeProject($user);
        $phase = $this->makePhase($project);

        Sanctum::actingAs($user, ['mobile-sync']);

        $this->getJson("/api/v1/projects/{$project->id}/bundle")
            ->assertOk()
            ->assertJsonPath('project.id', $project->id)
            ->assertJsonPath('phases.0.id', $phase->id);
    }

    // ── Push (sync) ──────────────────────────────────────────────────────────────

    public function test_sync_creates_progress_update_and_is_idempotent(): void
    {
        $user = User::factory()->create();
        $user->givePermissionTo('update progress');
        $project = $this->makeProject($user);
        $phase = $this->makePhase($project);
        $uuid = (string) Str::uuid();

        Sanctum::actingAs($user, ['mobile-sync']);

        $payload = ['operations' => [[
            'entity' => 'progress_update', 'op' => 'create', 'uuid' => $uuid,
            'client_updated_at' => now()->toIso8601String(),
            'data' => ['phase_id' => $phase->id, 'progress' => 75, 'comment' => 'Avance'],
        ]]];

        // First push → applied
        $this->postJson('/api/v1/sync', $payload)
            ->assertOk()
            ->assertJsonPath('results.0.status', 'applied');

        $this->assertDatabaseHas('progress_updates', ['uuid' => $uuid, 'progress_percent' => 75, 'user_id' => $user->id]);
        $this->assertEquals(75, $phase->fresh()->progress_percent);

        // Re-send same uuid → duplicate (no second row)
        $this->postJson('/api/v1/sync', $payload)
            ->assertOk()
            ->assertJsonPath('results.0.status', 'duplicate');

        $this->assertEquals(1, ProgressUpdate::where('uuid', $uuid)->count());
    }

    public function test_sync_returns_error_without_permission(): void
    {
        $user = User::factory()->create();           // member but WITHOUT 'update progress'
        $project = $this->makeProject($user);
        $phase = $this->makePhase($project);

        Sanctum::actingAs($user, ['mobile-sync']);

        $this->postJson('/api/v1/sync', ['operations' => [[
            'entity' => 'progress_update', 'op' => 'create', 'uuid' => (string) Str::uuid(),
            'data' => ['phase_id' => $phase->id, 'progress' => 50],
        ]]])
            ->assertOk()
            ->assertJsonPath('results.0.status', 'error');

        $this->assertDatabaseCount('progress_updates', 0);
    }

    // ── Pull: delta + tombstones + templates (Milestone 3) ───────────────────────

    public function test_bundle_delta_returns_only_changed_records(): void
    {
        $user = User::factory()->create();

        Carbon::setTestNow('2026-06-18 10:00:00');
        $project = $this->makeProject($user);
        $old = $this->makePhase($project, 'Fase vieja');

        $since = Carbon::parse('2026-06-18 10:00:30');

        Carbon::setTestNow('2026-06-18 10:05:00');
        $new = $this->makePhase($project, 'Fase nueva');

        Sanctum::actingAs($user, ['mobile-sync']);
        $res = $this->getJson("/api/v1/projects/{$project->id}/bundle?since=".urlencode($since->toIso8601String()))->assertOk();

        $ids = collect($res->json('phases'))->pluck('id');
        $this->assertTrue($ids->contains($new->id));
        $this->assertFalse($ids->contains($old->id));

        Carbon::setTestNow();
    }

    public function test_bundle_delta_includes_tombstones_for_deletions(): void
    {
        $user = User::factory()->create();

        Carbon::setTestNow('2026-06-18 10:00:00');
        $project = $this->makeProject($user);
        $feature = $this->makeFeature($this->makeLayer($this->makePhase($project)));

        $since = Carbon::parse('2026-06-18 10:00:30');

        Carbon::setTestNow('2026-06-18 10:05:00');
        $feature->delete(); // soft delete

        Sanctum::actingAs($user, ['mobile-sync']);
        $res = $this->getJson("/api/v1/projects/{$project->id}/bundle?since=".urlencode($since->toIso8601String()))->assertOk();

        $this->assertContains($feature->id, $res->json('deleted.features'));

        Carbon::setTestNow();
    }

    public function test_templates_endpoint_returns_accessible_templates_with_hash(): void
    {
        $user = User::factory()->create();
        $project = $this->makeProject($user);
        $tpl = InspectionTemplate::create([
            'name' => 'Plantilla A',
            'fields' => [['name' => 'ok', 'label' => 'OK', 'type' => 'boolean']],
        ]);
        $project->inspectionTemplates()->attach($tpl->id);

        Sanctum::actingAs($user, ['mobile-sync']);
        $res = $this->getJson('/api/v1/templates')->assertOk();

        $this->assertTrue(collect($res->json('templates'))->pluck('name')->contains('Plantilla A'));
        $this->assertNotNull($res->json('templates.0.hash'));
    }

    // ── Push: inspections / issues / features (Milestone 4) ──────────────────────

    public function test_sync_creates_inspection_and_is_idempotent(): void
    {
        $user = User::factory()->create();
        $user->givePermissionTo('create inspections');
        $project = $this->makeProject($user);
        $feature = $this->makeFeature($this->makeLayer($this->makePhase($project)));
        $uuid = (string) Str::uuid();

        Sanctum::actingAs($user, ['mobile-sync']);
        $payload = ['operations' => [[
            'entity' => 'inspection', 'op' => 'create', 'uuid' => $uuid,
            'data' => ['feature_id' => $feature->id, 'data' => ['ok' => true], 'result' => 'pass'],
        ]]];

        $this->postJson('/api/v1/sync', $payload)->assertOk()->assertJsonPath('results.0.status', 'applied');
        $this->assertDatabaseHas('inspections', ['uuid' => $uuid, 'feature_id' => $feature->id, 'user_id' => $user->id]);

        $this->postJson('/api/v1/sync', $payload)->assertOk()->assertJsonPath('results.0.status', 'duplicate');
        $this->assertEquals(1, Inspection::where('uuid', $uuid)->count());
    }

    public function test_sync_creates_and_updates_an_issue(): void
    {
        $user = User::factory()->create();
        $user->givePermissionTo(['create issues', 'edit issues']);
        $project = $this->makeProject($user);

        Sanctum::actingAs($user, ['mobile-sync']);

        // create
        $uuid = (string) Str::uuid();
        $this->postJson('/api/v1/sync', ['operations' => [[
            'entity' => 'issue', 'op' => 'create', 'uuid' => $uuid,
            'data' => ['project_id' => $project->id, 'title' => 'Grieta', 'priority' => 'high', 'type' => 'safety'],
        ]]])->assertOk()->assertJsonPath('results.0.status', 'applied');

        $issue = Issue::where('uuid', $uuid)->firstOrFail();
        $this->assertEquals('open', $issue->status);
        $this->assertEquals('safety', $issue->type);

        // update (resolve) → resolved_at set
        $this->postJson('/api/v1/sync', ['operations' => [[
            'entity' => 'issue', 'op' => 'update', 'uuid' => (string) Str::uuid(),
            'data' => ['id' => $issue->id, 'status' => 'resolved', 'resolution_notes' => 'Sellada'],
        ]]])->assertOk()->assertJsonPath('results.0.status', 'applied');

        $this->assertEquals('resolved', $issue->fresh()->status);
        $this->assertNotNull($issue->fresh()->resolved_at);
    }

    public function test_sync_updates_feature_and_recomputes_phase_progress(): void
    {
        $user = User::factory()->create();
        $user->givePermissionTo('update progress');
        $project = $this->makeProject($user);
        $phase = $this->makePhase($project);
        $feature = $this->makeFeature($this->makeLayer($phase));

        Sanctum::actingAs($user, ['mobile-sync']);
        $this->postJson('/api/v1/sync', ['operations' => [[
            'entity' => 'feature', 'op' => 'update', 'uuid' => (string) Str::uuid(),
            'data' => ['id' => $feature->id, 'status' => 'completed', 'progress' => 100],
        ]]])->assertOk()->assertJsonPath('results.0.status', 'applied');

        $this->assertEquals(100, $feature->fresh()->progress);
        $this->assertEquals(100, $phase->fresh()->progress_percent);
    }

    public function test_feature_update_sets_active_and_type_and_bundle_exposes_them(): void
    {
        $user = User::factory()->create();
        $user->givePermissionTo('update progress');
        $project = $this->makeProject($user);
        $feature = $this->makeFeature($this->makeLayer($this->makePhase($project)));
        $type = FeatureType::create(['name' => 'Pilar', 'color' => '#ffffff']);

        Sanctum::actingAs($user, ['mobile-sync']);
        $this->postJson('/api/v1/sync', ['operations' => [[
            'entity' => 'feature', 'op' => 'update', 'uuid' => (string) Str::uuid(),
            'data' => ['id' => $feature->id, 'is_active' => false, 'feature_type_id' => $type->id],
        ]]])->assertOk()->assertJsonPath('results.0.status', 'applied');

        $feature->refresh();
        $this->assertFalse($feature->is_active);
        $this->assertEquals($type->id, $feature->feature_type_id);

        $res = $this->getJson("/api/v1/projects/{$project->id}/bundle")->assertOk();
        $this->assertEquals($type->id, $res->json('features.0.feature_type_id'));
        $this->assertFalse($res->json('features.0.is_active'));
        $this->assertTrue(collect($res->json('feature_types'))->pluck('id')->contains($type->id));
    }

    public function test_sync_returns_conflict_when_server_is_newer(): void
    {
        $user = User::factory()->create();
        $user->givePermissionTo('update progress');
        $project = $this->makeProject($user);

        Carbon::setTestNow('2026-06-18 12:00:00');
        $feature = $this->makeFeature($this->makeLayer($this->makePhase($project)));
        Carbon::setTestNow(); // feature->updated_at = 12:00

        Sanctum::actingAs($user, ['mobile-sync']);
        // client edited BEFORE the server's last update → conflict
        $res = $this->postJson('/api/v1/sync', ['operations' => [[
            'entity' => 'feature', 'op' => 'update', 'uuid' => (string) Str::uuid(),
            'client_updated_at' => '2026-06-18 11:00:00',
            'data' => ['id' => $feature->id, 'progress' => 50],
        ]]])->assertOk();

        $res->assertJsonPath('results.0.status', 'conflict');
        $this->assertEquals(0, $feature->fresh()->progress); // unchanged
    }

    // ── Media (Milestone 5) + op idempotency via sync_logs (Milestone 6) ─────────

    public function test_media_upload_attaches_to_feature_and_is_idempotent(): void
    {
        Storage::fake('public');

        $user = User::factory()->create();
        $user->givePermissionTo('upload media');
        $project = $this->makeProject($user);
        $feature = $this->makeFeature($this->makeLayer($this->makePhase($project)));
        $uuid = (string) Str::uuid();

        Sanctum::actingAs($user, ['mobile-sync']);
        $payload = [
            'uuid' => $uuid, 'parent_entity' => 'feature', 'parent_id' => $feature->id,
            'file' => UploadedFile::fake()->image('foto.jpg'),
        ];

        $this->post('/api/v1/media', $payload)->assertOk()->assertJsonPath('status', 'applied');
        $this->assertDatabaseHas('media', [
            'uuid' => $uuid, 'mediable_type' => Feature::class, 'mediable_id' => $feature->id,
        ]);

        // Re-send same uuid → duplicate, no second row
        $this->post('/api/v1/media', [
            'uuid' => $uuid, 'parent_entity' => 'feature', 'parent_id' => $feature->id,
            'file' => UploadedFile::fake()->image('foto.jpg'),
        ])->assertOk()->assertJsonPath('status', 'duplicate');

        $this->assertEquals(1, Media::where('uuid', $uuid)->count());
    }

    // ── Issue tasks / comments (enriquecimiento incidencias) ─────────────────────

    private function makeIssue(Project $project, ?User $reporter = null): Issue
    {
        return Issue::create([
            'project_id' => $project->id,
            'title' => 'Incidencia test',
            'status' => 'open',
            'priority' => 'medium',
            'reported_by' => $reporter?->id ?? $project->created_by,
        ]);
    }

    public function test_sync_creates_and_updates_an_issue_task(): void
    {
        $user = User::factory()->create();
        $user->givePermissionTo('edit issues');
        $project = $this->makeProject($user);
        $issue = $this->makeIssue($project, $user);

        Sanctum::actingAs($user, ['mobile-sync']);

        // create task
        $uuid = (string) Str::uuid();
        $this->postJson('/api/v1/sync', ['operations' => [[
            'entity' => 'issue_task', 'op' => 'create', 'uuid' => $uuid,
            'data' => ['issue_id' => $issue->id, 'title' => 'Sanear zona'],
        ]]])->assertOk()->assertJsonPath('results.0.status', 'applied');

        $task = IssueTask::where('uuid', $uuid)->firstOrFail();
        $this->assertFalse($task->is_done);

        // mark done via update
        $this->postJson('/api/v1/sync', ['operations' => [[
            'entity' => 'issue_task', 'op' => 'update', 'uuid' => (string) Str::uuid(),
            'data' => ['id' => $task->id, 'is_done' => true],
        ]]])->assertOk()->assertJsonPath('results.0.status', 'applied');

        $task->refresh();
        $this->assertTrue($task->is_done);
        $this->assertEquals($user->id, $task->done_by);
        $this->assertNotNull($task->done_at);
    }

    public function test_sync_issue_task_is_forbidden_without_edit_permission(): void
    {
        $user = User::factory()->create();           // member but no 'edit issues'
        $project = $this->makeProject($user);
        $issue = $this->makeIssue($project, $user);

        Sanctum::actingAs($user, ['mobile-sync']);
        $this->postJson('/api/v1/sync', ['operations' => [[
            'entity' => 'issue_task', 'op' => 'create', 'uuid' => (string) Str::uuid(),
            'data' => ['issue_id' => $issue->id, 'title' => 'X'],
        ]]])->assertOk()->assertJsonPath('results.0.status', 'error');

        $this->assertDatabaseCount('issue_tasks', 0);
    }

    public function test_sync_creates_an_issue_comment(): void
    {
        $user = User::factory()->create();
        $user->givePermissionTo('view issues');
        $project = $this->makeProject($user);
        $issue = $this->makeIssue($project, $user);

        Sanctum::actingAs($user, ['mobile-sync']);
        $uuid = (string) Str::uuid();
        $this->postJson('/api/v1/sync', ['operations' => [[
            'entity' => 'issue_comment', 'op' => 'create', 'uuid' => $uuid,
            'data' => ['issue_id' => $issue->id, 'body' => 'Revisado en obra'],
        ]]])->assertOk()->assertJsonPath('results.0.status', 'applied');

        $this->assertDatabaseHas('issue_comments', [
            'uuid' => $uuid, 'issue_id' => $issue->id, 'user_id' => $user->id, 'body' => 'Revisado en obra',
        ]);
    }

    public function test_media_uploads_to_issue_task_and_comment(): void
    {
        Storage::fake('public');

        $user = User::factory()->create();
        $user->givePermissionTo('upload media');
        $project = $this->makeProject($user);
        $issue = $this->makeIssue($project, $user);
        $task = $issue->tasks()->create(['title' => 'T', 'uuid' => (string) Str::uuid()]);
        $comment = $issue->comments()->create(['user_id' => $user->id, 'body' => 'c', 'uuid' => (string) Str::uuid()]);

        Sanctum::actingAs($user, ['mobile-sync']);

        $this->post('/api/v1/media', [
            'uuid' => (string) Str::uuid(), 'parent_entity' => 'issue_task', 'parent_id' => $task->id,
            'file' => UploadedFile::fake()->image('a.jpg'),
        ])->assertOk()->assertJsonPath('status', 'applied');

        $this->post('/api/v1/media', [
            'uuid' => (string) Str::uuid(), 'parent_entity' => 'issue_comment', 'parent_id' => $comment->id,
            'file' => UploadedFile::fake()->image('b.jpg'),
        ])->assertOk()->assertJsonPath('status', 'applied');

        $this->assertDatabaseHas('media', ['mediable_type' => IssueTask::class, 'mediable_id' => $task->id]);
        $this->assertDatabaseHas('media', ['mediable_type' => IssueComment::class, 'mediable_id' => $comment->id]);
    }

    public function test_bundle_includes_issue_tasks_and_comments(): void
    {
        $user = User::factory()->create();
        $project = $this->makeProject($user);
        $issue = $this->makeIssue($project, $user);
        $task = $issue->tasks()->create(['title' => 'Tarea bundle', 'uuid' => (string) Str::uuid()]);
        $comment = $issue->comments()->create(['user_id' => $user->id, 'body' => 'Comentario bundle', 'uuid' => (string) Str::uuid()]);

        Sanctum::actingAs($user, ['mobile-sync']);
        $res = $this->getJson("/api/v1/projects/{$project->id}/bundle")->assertOk();

        $this->assertTrue(collect($res->json('issue_tasks'))->pluck('id')->contains($task->id));
        $this->assertTrue(collect($res->json('issue_comments'))->pluck('id')->contains($comment->id));
    }

    public function test_sync_operation_is_idempotent_via_sync_logs(): void
    {
        $user = User::factory()->create();
        $user->givePermissionTo('update progress');
        $project = $this->makeProject($user);
        $feature = $this->makeFeature($this->makeLayer($this->makePhase($project)));
        $opUuid = (string) Str::uuid();

        Sanctum::actingAs($user, ['mobile-sync']);
        $op = ['operations' => [[
            'entity' => 'feature', 'op' => 'update', 'uuid' => $opUuid,
            'data' => ['id' => $feature->id, 'progress' => 60],
        ]]];

        $this->postJson('/api/v1/sync', $op)->assertOk()->assertJsonPath('results.0.status', 'applied');
        $this->assertEquals(1, SyncLog::where('op_uuid', $opUuid)->count());

        // Replaying the same operation uuid → duplicate (served from sync_logs)
        $this->postJson('/api/v1/sync', $op)->assertOk()->assertJsonPath('results.0.status', 'duplicate');
        $this->assertEquals(1, SyncLog::where('op_uuid', $opUuid)->count());
    }
}
