<?php

namespace Tests\Feature;

use App\Models\Branch;
use App\Models\Business;
use App\Models\ContractTemplate;
use App\Models\Customer;
use App\Models\Loan;
use App\Models\LoanProduct;
use App\Models\User;
use App\Services\ContractPreviewEditorService;
use App\Services\DocxContractTemplateService;
use App\Services\PdfContractTemplateService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
use ZipArchive;

class ContractTemplateWorkflowTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(DatabaseSeeder::class);
        Storage::fake('local');
    }

    public function test_contract_template_admin_pages_are_available_for_docx_and_pdf(): void
    {
        $admin=User::where('username','admin')->firstOrFail();
        $this->actingAs($admin)->get(route('admin.contract-templates.index'))
            ->assertOk()->assertSee('Contract Templates')->assertSee('Upload Contract');
        $this->actingAs($admin)->get(route('admin.contract-templates.create'))
            ->assertOk()->assertSee('Upload Contract Template')->assertSee('DOCX or PDF contract')->assertSee('Required signatures');
    }

    public function test_pdf_contract_can_be_uploaded_as_a_template(): void
    {
        $admin=User::where('username','admin')->firstOrFail();
        $response=$this->actingAs($admin)->post(route('admin.contract-templates.store'),[
            'name'=>'Uploaded PDF Contract',
            'template_file'=>UploadedFile::fake()->createWithContent('loan-contract.pdf',$this->samplePdf()),
            'required_signatories'=>['borrower','loan_officer'],
        ]);

        $template=ContractTemplate::where('name','Uploaded PDF Contract')->firstOrFail();
        $response->assertRedirect(route('admin.contract-templates.edit',$template));
        $this->assertSame('pdf',$template->format);
        $this->assertCount(1,$template->blocks ?? []);
        $this->assertSame([],array_values($template->mappings ?? []));
    }

    public function test_template_mapping_editor_is_the_single_contract_correction_point(): void
    {
        [, $template]=$this->loanAndTemplate();
        $admin=User::where('username','admin')->firstOrFail();
        $this->actingAs($admin)->get(route('admin.contract-templates.edit',$template))
            ->assertOk()
            ->assertSee('Finalize the contract here.')
            ->assertSee('Detected mappings & spacing')
            ->assertSee('Before')
            ->assertSee('After')
            ->assertSee('Trim')
            ->assertSee('Loan users do not edit contract values, spacing or positions.');
    }

    public function test_pdf_template_editor_exposes_original_pdf_and_final_coordinate_controls(): void
    {
        [, $template]=$this->pdfLoanAndTemplate();
        $admin=User::where('username','admin')->firstOrFail();
        $this->actingAs($admin)->get(route('admin.contract-templates.edit',$template))
            ->assertOk()
            ->assertSee('PDF overlay fields')
            ->assertSee('X mm')
            ->assertSee('Y mm')
            ->assertSee('Font')
            ->assertSee('Original PDF')
            ->assertSee('These saved positions are used directly for every generated loan contract.');
        $this->actingAs($admin)->get(route('admin.contract-templates.preview-source',$template))
            ->assertOk()->assertHeader('Content-Type','application/pdf');
    }

    public function test_docx_mapping_fills_known_values_and_preserves_unmapped_signature_area(): void
    {
        [$loan,$template]=$this->loanAndTemplate();
        $generated=$this->inBranch((int)$loan->branch_id, fn () => app(DocxContractTemplateService::class)->generate($template,$loan));
        $xml=$this->documentXml($generated);
        $this->assertStringContainsString('Amina Test',$xml);
        $this->assertStringContainsString('19900101123456789012',$xml);
        $this->assertStringContainsString('10,000.00',$xml);
        $this->assertStringContainsString('SAHIHI __________________',$xml);
    }

    public function test_pdf_template_service_preserves_pdf_and_overlays_system_value(): void
    {
        [$loan,$template]=$this->pdfLoanAndTemplate();
        $generated=$this->inBranch((int)$loan->branch_id, fn () => app(ContractPreviewEditorService::class)->generate($template,$loan));
        $this->assertStringStartsWith('%PDF-',$generated);
        $this->assertStringContainsString('Amina Test',$generated);
        $this->assertGreaterThan(strlen($this->samplePdf()),strlen($generated));
    }

    public function test_obsolete_loan_contract_preview_routes_redirect_back_to_loan(): void
    {
        [$loan]=$this->loanAndTemplate();
        $admin=User::where('username','admin')->firstOrFail();

        $this->actingAs($admin)->get(route('loans.contract-preview',$loan))
            ->assertRedirect(route('loans.show',$loan));
        $this->actingAs($admin)->post(route('loans.contract-preview.refresh',$loan))
            ->assertRedirect(route('loans.show',$loan));
        $this->actingAs($admin)->get(route('loans.contract-preview.pdf',$loan))
            ->assertRedirect(route('loans.show',$loan));
    }

    public function test_saved_docx_template_spacing_is_applied_without_loan_level_overrides(): void
    {
        [$loan,$template]=$this->loanAndTemplate();
        $mappings=array_values($template->mappings ?? []);
        $nameIndex=collect($mappings)->search(fn($mapping)=>($mapping['field'] ?? '')==='customer.full_name');
        $this->assertNotFalse($nameIndex);

        $mappings[(int)$nameIndex]['spaces_before']=2;
        $mappings[(int)$nameIndex]['spaces_after']=1;
        $mappings[(int)$nameIndex]['trim_surrounding']=true;
        $template->update(['mappings'=>$mappings]);

        $generated=$this->inBranch((int)$loan->branch_id, fn () => app(ContractPreviewEditorService::class)->generate($template->fresh(),$loan));
        $xml=$this->documentXml($generated);
        $this->assertStringContainsString('Jina la mteja</w:t>',$xml);
        $this->assertStringContainsString('xml:space="preserve">  Amina Test </w:t>',$xml);
    }

    public function test_generate_contract_route_directly_uses_default_docx_template_and_stores_generated_copy(): void
    {
        [$loan,$template]=$this->loanAndTemplate();
        $admin=User::where('username','admin')->firstOrFail();
        $response=$this->actingAs($admin)->post(route('loans.contract',$loan));
        $response->assertOk();
        $this->assertStringContainsString('application/vnd.openxmlformats-officedocument.wordprocessingml.document',(string)$response->headers->get('Content-Type'));
        $this->assertStringContainsString('Amina Test',$this->documentXml($response->getContent()));
        $loan->refresh();
        $this->assertSame($template->id,$loan->contract_template_id);
        $this->assertNotNull($loan->contract_generated_at);
        $this->assertDatabaseHas('loan_documents',['loan_id'=>$loan->id,'kind'=>'generated_contract','document_type'=>'Generated Loan Contract']);
    }

    public function test_generate_contract_route_directly_finalizes_pdf_template_and_stores_pdf_copy(): void
    {
        [$loan,$template]=$this->pdfLoanAndTemplate();
        $admin=User::where('username','admin')->firstOrFail();
        $response=$this->actingAs($admin)->post(route('loans.contract',$loan));
        $response->assertOk()->assertHeader('Content-Type','application/pdf');
        $this->assertStringStartsWith('%PDF-',$response->getContent());
        $this->assertStringContainsString('Amina Test',$response->getContent());
        $loan->refresh();
        $this->assertSame($template->id,$loan->contract_template_id);
        $this->assertNotNull($loan->contract_generated_at);
        $this->assertDatabaseHas('loan_documents',[
            'loan_id'=>$loan->id,'kind'=>'generated_contract','document_type'=>'Generated Loan Contract','mime_type'=>'application/pdf',
        ]);
    }

    public function test_loan_generation_ignores_submitted_value_and_layout_overrides(): void
    {
        [$loan]=$this->loanAndTemplate();
        $admin=User::where('username','admin')->firstOrFail();

        $response=$this->actingAs($admin)->post(route('loans.contract',$loan),[
            'overrides'=>[0=>'Injected loan-level value'],
            'formatting'=>[0=>['spaces_before'=>9,'spaces_after'=>9,'trim_surrounding'=>1]],
        ]);

        $response->assertOk();
        $xml=$this->documentXml($response->getContent());
        $this->assertStringContainsString('Amina Test',$xml);
        $this->assertStringNotContainsString('Injected loan-level value',$xml);
    }

    public function test_loan_page_offers_generation_not_contract_correction(): void
    {
        [$loan]=$this->loanAndTemplate();
        $admin=User::where('username','admin')->firstOrFail();

        $this->actingAs($admin)->get(route('loans.show',$loan))
            ->assertOk()
            ->assertSee('Generate contract')
            ->assertSee('Generate completed contract')
            ->assertDontSee('Preview / generate contract')
            ->assertDontSee('Preview contract');
    }

    /** @return array{0:Loan,1:ContractTemplate} */
    private function pdfLoanAndTemplate(): array
    {
        [$loan,$docxTemplate]=$this->loanAndTemplate();
        $docxTemplate->update(['status'=>'inactive','is_default'=>false]);
        $business=$loan->branch->business;
        $admin=User::where('username','admin')->firstOrFail();
        $path='contract-templates/'.$business->id.'/sample.pdf';
        Storage::disk('local')->put($path,$this->samplePdf());
        $inspection=app(PdfContractTemplateService::class)->inspectPath(Storage::disk('local')->path($path));
        $template=ContractTemplate::create([
            'business_id'=>$business->id,'name'=>'Test PDF Contract','version'=>1,'original_name'=>'sample.pdf','format'=>'pdf','file_path'=>$path,
            'extracted_text'=>$inspection['extracted_text'],'blocks'=>$inspection['blocks'],
            'mappings'=>[[
                'block_id'=>'pdf:1','segment_index'=>-1,'field'=>'customer.full_name','mode'=>'overlay','label'=>'Customer name PDF overlay',
                'page'=>1,'x'=>48,'y'=>18,'width'=>100,'font_size'=>11,'line_height'=>5,'align'=>'L',
            ]],
            'required_signatories'=>['borrower','loan_officer'],'status'=>'active','is_default'=>true,'uploaded_by'=>$admin->id,'updated_by'=>$admin->id,
        ]);
        return [$loan,$template];
    }

    /** @return array{0:Loan,1:ContractTemplate} */
    private function loanAndTemplate(): array
    {
        $business=Business::firstOrFail();
        $branch=Branch::create(['business_id'=>$business->id,'name'=>'Contract Test Branch','location'=>'Dodoma','status'=>'active']);
        $customer=Customer::create([
            'branch_id'=>$branch->id,'customer_number'=>'CUS-CONTRACT-1','identity_mode'=>'with_nin','first_name'=>'Amina','last_name'=>'Test','gender'=>'female','date_of_birth'=>'1990-01-01',
            'nin'=>'19900101123456789012','phone'=>'0712345678','occupation'=>'Trader','marital_status'=>'married','status'=>'active',
        ]);
        $customer->address()->create(['region'=>'Dodoma','district'=>'Dodoma City','ward'=>'Kizota','street_village'=>'Mtaa wa Test']);
        $product=LoanProduct::create([
            'business_id'=>$business->id,'name'=>'Contract Product','minimum_amount'=>'1000.00','maximum_amount'=>'50000.00','duration'=>1,'duration_unit'=>'months','interest_rate'=>'0.000000',
            'interest_calculation_method'=>'flat','repayment_frequency'=>'monthly','grace_period_days'=>0,'late_payment_penalty_type'=>'none','late_payment_penalty_value'=>'0.000000',
            'processing_fee_type'=>'fixed','processing_fee_value'=>'0.000000','processing_fee_payment_mode'=>'financed','insurance_fee_type'=>'fixed','insurance_fee_value'=>'0.000000',
            'other_charges'=>'0.00','collateral_required'=>false,'guarantor_required'=>false,'status'=>'active',
        ]);
        $admin=User::where('username','admin')->firstOrFail();
        $loan=Loan::create([
            'branch_id'=>$branch->id,'customer_id'=>$customer->id,'loan_product_id'=>$product->id,'sequence_number'=>1,'sequence_year'=>2026,'loan_number'=>'LN-CONTRACT-0001',
            'principal'=>'10000.00','interest'=>'0.00','processing_fee'=>'0.00','processing_fee_payment_mode'=>'financed','insurance_fee'=>'0.00','other_charges'=>'0.00','fees'=>'0.00','total_repayment'=>'10000.00',
            'purpose'=>'Business stock','schedule_start_date'=>'2026-08-15','status'=>'draft','created_by'=>$admin->id,'updated_by'=>$admin->id,
        ]);
        $loan->installments()->create([
            'installment_number'=>1,'principal'=>'10000.00','interest'=>'0.00','fees'=>'0.00','amount_due'=>'10000.00','due_date'=>'2026-09-15','amount_paid'=>'0.00','remaining'=>'10000.00',
            'status'=>'upcoming','principal_paid'=>'0.00','interest_paid'=>'0.00','fees_paid'=>'0.00','penalty_due'=>'0.00','penalty_paid'=>'0.00',
        ]);
        $path='contract-templates/'.$business->id.'/sample.docx';
        Storage::disk('local')->put($path,$this->sampleDocx());
        $inspection=app(DocxContractTemplateService::class)->inspectPath(Storage::disk('local')->path($path));
        $this->assertGreaterThanOrEqual(3,count($inspection['mappings']));
        $mappings=collect($inspection['mappings'])->map(function(array $mapping): array {
            $mapping['spaces_before']=($mapping['mode'] ?? '')==='append'?1:0;
            $mapping['spaces_after']=0;
            $mapping['trim_surrounding']=false;
            return $mapping;
        })->all();
        $template=ContractTemplate::create([
            'business_id'=>$business->id,'name'=>'Test Standard Contract','version'=>1,'original_name'=>'sample.docx','format'=>'docx','file_path'=>$path,'extracted_text'=>$inspection['extracted_text'],
            'blocks'=>$inspection['blocks'],'mappings'=>$mappings,'required_signatories'=>['borrower','loan_officer','office_stamp'],'status'=>'active','is_default'=>true,'uploaded_by'=>$admin->id,'updated_by'=>$admin->id,
        ]);
        return [$loan,$template];
    }

    private function samplePdf(): string
    {
        $pdf=new \FPDF('P','mm','A4');
        $pdf->SetCompression(false);
        $pdf->AddPage();
        $pdf->SetFont('Helvetica','',12);
        $pdf->Text(20,20,'Customer:');
        $pdf->Text(20,35,'Loan agreement sample background');
        return $pdf->Output('S');
    }

    private function sampleDocx(): string
    {
        $tmp=tempnam(sys_get_temp_dir(),'docx-test-');
        $zip=new ZipArchive();
        $zip->open($tmp,ZipArchive::OVERWRITE);
        $zip->addFromString('[Content_Types].xml','<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/></Types>');
        $zip->addFromString('_rels/.rels','<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>');
        $zip->addFromString('word/document.xml','<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>TAARIFA BINAFSI ZA MWOMBAJI</w:t></w:r></w:p><w:p><w:r><w:t xml:space="preserve">Jina la mteja </w:t></w:r><w:r><w:t>………………………………</w:t></w:r></w:p><w:p><w:r><w:t>Namba ya nida </w:t></w:r><w:r><w:t>………………………………</w:t></w:r></w:p><w:p><w:r><w:t>kiasi cha Tsh </w:t></w:r><w:r><w:t>………………………………</w:t></w:r></w:p><w:p><w:r><w:t>SAHIHI __________________</w:t></w:r></w:p><w:sectPr/></w:body></w:document>');
        $zip->close();
        $bytes=file_get_contents($tmp); @unlink($tmp); return (string)$bytes;
    }

    private function documentXml(string $docxBytes): string
    {
        $tmp=tempnam(sys_get_temp_dir(),'generated-docx-'); file_put_contents($tmp,$docxBytes);
        $zip=new ZipArchive(); $zip->open($tmp); $xml=(string)$zip->getFromName('word/document.xml'); $zip->close(); @unlink($tmp); return $xml;
    }
}
